読者です 読者をやめる 読者になる 読者になる

てくてくテック

気ままに開発のメモを書いていこうと思います。主にSwiftかと。

youtube-ios-player-helperで再生できないことがある

iOS 技術

はじめに

youtube の動画でたまに再生されないものがあったので、それについてのお話

現象

youtube-ios-player-helperを使ってyoutubeの動画再生していました。 大半の動画は再生されるのですが、一部の動画(ex. ID: A_bdeYlpasE)で再生されませんでした。 再生されないというのも、押した反応はあるのですが、その後何も起きないという感じでした(下図参照)。 https://gyazo.com/6f3708a2a932c326362ada6904dfc047

また、全デリゲートメソッドを監視してみましたが特にエラーが出たりもしませんでした。

原因と対処法

何回もやっていたら偶然か違う挙動をしました。

f:id:k-s-9190118:20161111105648p:plain

これを元にライブラリのgithubを調べてみたら、issueが幾つか上がってます#197。 対応としてはパラメーターにoriginを追加すれば良いようです。

self.playerView?.load(withVideoId: "A_bdeYlpasE", playerVars: ["playsinline":1, "origin" : "https://www.youtube.com"]

Mac Book Pro頼んだら決済が通ってなかった

はじめに

先月とうとう発表された新Mac Book Proを意気揚々と予約して今か今かと待ちわびてたんですが ふとApple Storeのご注文一覧のページにステータスの確認をしに行ったら お支払い確認中となってましたorz

状況確認

  • 発表された日にMac Book Proを予約
  • クレカで12分割払い
  • Appleの右上のご注文からご注文一覧見に行った
  • なんか赤い文字でエラーっぽい
  • クレカ情報入れ直したり別のでやったけどダメ
  • メール見に行ったけど何も来てなかった

お支払い確認中

お支払いの確認または承認の問い合わせ中です。この時点では正式なご注文として成立しておりません。ご注文から14日以内(後払いは30日以内)にお支払いまたはお手続きをお願いします。期日までにお支払いの確認または承認が取れない場合は、ご注文は自動的にキャンセルとなります。

ショッピングのサポートより

何かしないと自動でキャンセルになってしまうので自分から動きましょう

対応

ご注文一覧のページでは何もできなかったのでAppleのサポートに電話しました。 0120-993-993

自動案内でまず4を押し,ご注文一覧ページにあるWから始まる注文番号のWを除いた数字を入力すると担当者につながります。

状況を説明したらクレカの上限とかで決済できなかったのでは?と確認されたがWebで確認した感じ上限は残っていましたし、複数のクレカでダメだったのでそう伝えました。 すると、こういう場合、分割払いにしているのを1割に変更すると通る人が多いと返答がきました。分割から少ない分割にするということはできず、分割から1割に変更するということしかできないそうなので、1割に変更してもらいました。この結果きちんと決済が通りました。よかったε-(´∀`*)ホッ

Reporterを使ってiTunes ConnectのSales and Trendsを自動で取得する - 解析編

iOS ruby 技術

はじめに

今回はReporterを使ってiTunes ConnectのSales and Trendsを自動で取得する - 設計編におけるdaily reportの"iTunes Connectから取得"と"インストール数の取得"のサンプルを実装しようと思います。最終的な実装とはちょっと違いますが基本は同じです。

環境

環境 version
OS OS X EL Capitan 10.11.6
ruby 2.3.1(rbenv)

実装方針

  1. シェルコマンドを実行してreporterを実行
  2. Zlib::GzipReaderを使ってgzファイルを解凍
  3. ターゲットとしているアプリかつProduct typeのunitsをかき集める

シェルコマンドを実行してreporterを実行

reporter自体はjavaで実行なのでrubyからシェルコマンドを実行する仕組みを実装します。Rubyで外部コマンドを実行して結果を受け取る方法あれこれを見るといろいろあるみたいですけど一番簡単そうなバッククォートで良さそうです。

date = "20161101"
report_xml = `java -jar Reporter.jar p=Reporter.properties m=Robot.XML Sales.getReport <vendor id>, Sales, Summary, Daily, #{date}` 

Zlib::GzipReaderを使ってgzファイルを解凍

サンプル見るのが早いかと思います。

require 'zlib'

date = "20161101"
file_name = "S_D_<vendor id>_#{date}.txt.gz"

Zlib::GzipReader.open(file_name){|gz|
# 最初のlineはムシ
gz.gets

while s = gz.gets
    p s
}

ターゲットとしているアプリかつProduct typeのunitsをかき集める

今回は簡略化のため一つのアプリに限定してます。
ここで問題となるのが、取得できるデータが各アプリのインストール毎という形式ではなく、各アプリのProduct Typeや地域毎という感じで別れてしまっていることです。今回は、ターゲットとしたアプリの全地域でのインストール数を取得するという方針でいきます。

require 'zlib'

# http://help.apple.com/itc/appssalesandtrends/#/itc0c699d615
# アップデートとかを集計しないため
TargetProductIds = ["1", "1F", "1T"].freeze
TargetBundleId = "hogehoge".freeze

date = "20161101"
file_name = "S_D_<vendor id>_#{date}.txt.gz"
app_unitis = 0

Zlib::GzipReader.open(file_name){|gz|
    # 最初のlineはムシ
    gz.gets

    while s = gz.gets
        # tsvでタグ区切りなので
        rows = s.split("\t")

        # 正確にはSKUだったけどほとんどの場合bundle_idと同じにするだろうからこのままでorz
        bundle_id = rows[2]
        units = rows[7].to_i
        product_type_id = rows[6]

        if !TargetProductIds.include?(product_type_id)
            next
        end

        if bundle_id == TargetBundleId
            app_units += units
        end
    end
}

p app_units

コード全部

require 'rexml/document'
require 'zlib'
# http://help.apple.com/itc/appssalesandtrends/#/itc0c699d615
# アップデートとかを集計しないため
TargetProductIds = ["1", "1F", "1T"].freeze
TargetBundleId = "hogehoge".freeze
VendorId = 1111111.freeze

date = "20161101"
report_xml = `java -jar Reporter.jar p=Reporter.properties m=Robot.XML Sales.getReport #{VendorId}, Sales, Summary, Daily, #{date}` 

report_result = REXML::Document.new(get_report_xml)
unless report_result.elements['Error/Code'].nil?
    p get_report_result.elements['Error/Code'].text
    p get_report_result.elements['Error/Message'].text
    # TODO: きちんと例外処理
    raise "Reporterのダウンロードに失敗しました"
end

file_name = "S_D_#{VendorId}_#{date}.txt.gz"
app_unitis = 0

Zlib::GzipReader.open(file_name){|gz|
    # 最初のlineはムシ
    gz.gets

    while s = gz.gets
        # tsvでタグ区切りなので
        rows = s.split("\t")

        # 正確にはSKUだったけどほとんどの場合bundle_idと同じにするだろうからこのままでorz
        bundle_id = rows[2]
        units = rows[7].to_i
        product_type_id = rows[6]

        if !TargetProductIds.include?(product_type_id)
            next
        end

        if bundle_id == TargetBundleId
            app_units += units
        end
    end
}

p app_units

SpreadsheetをRubyで操作する

ruby Spreadsheet 技術

はじめに

Reporterを使ってiTunes ConnectのSales and Trendsを自動で取得する - 設計編で実装指針としては書いていたGoogle SpreadsheetとRubyの連携について書きます。
なお環境は下記の通りです。

環境 version
OS OS X EL Capitan 10.11.6
ruby 2.3.1(rbenv)

ライブラリ(gem)

google_driveというgemを利用します。参考ページは下記の通りです。

実装

事前準備

誰でも好き勝手にSpreadsheetを操作できるようにしてしまうのはまずいので認証が必要です。そのための準備をします。

サービスアカウントの作成

On behalf of no existing users (service account)こちらの手順に従ってもらえれば、認証用のjsonファイルがダウンロードされます。

スプレッドシートの共有設定

先ほど作成したサービスアカウントのメールアドレス(jsonに書いてあります)を操作対象のSpreadsheetで共有します。

bundlerのインストール

gemの管理をbundlerを利用したいのでbundlerをinstallします。

  • $ cd path/to/directory
  • $ rbenv exec gem install bundler

google_driveのインストール

Gemfileというファイルを作成して下記を書き込みます。

source 'https://rubygems.org'
gem 'google_drive'

そしてgemをインストールします。
$ bundle install --path vendor/bundle

Example実装

Example to read/write spreadsheetsを実行してみます。

準備

  • サービスアカウントを作成した時にダウンロードしたjsonをrubyプログラムと同じディレクトリに config.jsonという名前で置きます
  • 操作対象のスプレッドシートキーを取得します
    • https://docs.google.com/spreadsheets/d/<スプレッドシートキー>/editみたいな感じになってるのでそこから取得できます(ちゃんとどこかに書いてあるとは思いますが...)

サンプルコード

自分はgithubのコードにrequire 'bundler/setup'を追加しないと動きませんでした。またsessionの作り方もサービスアカウントを使う場合に書き換えています。

require 'bundler/setup'
require "google_drive"

# Creates a session. This will prompt the credential via command line for the
# first time and save it to config.json file for later usages.
session =  GoogleDrive::Session.from_service_account_key("config.json")

# First worksheet of
# https://docs.google.com/spreadsheet/ccc?key=pz7XtlQC-PYx-jrVMJErTcg
# Or https://docs.google.com/a/someone.com/spreadsheets/d/pz7XtlQC-PYx-jrVMJErTcg/edit?usp=drive_web
ws = session.spreadsheet_by_key(<スプレッドシートキー>).worksheets[0]

# Gets content of A2 cell.
p ws[2, 1]  #==> "hoge"

# Changes content of cells.
# Changes are not sent to the server until you call ws.save().
ws[2, 1] = "foo"
ws[2, 2] = "bar"
ws.save

# Dumps all cells.
(1..ws.num_rows).each do |row|
  (1..ws.num_cols).each do |col|
    p ws[row, col]
  end
end

# Yet another way to do so.
p ws.rows  #==> [["fuga", ""], ["foo", "bar]]

# Reloads the worksheet to get changes by other clients.
ws.reload

APIピックアップ

Session作成

該当箇所: session = GoogleDrive::Session.from_service_account_key("config.json")

GoogleDrive/Sessionにあるように認証のやり方でメソッドが変わります。今回はサービスアカウントを利用したためExampleとは違い.from_service_account_key(json_key_path_or_io, scope = DEFAULT_SCOPE) ⇒ Objectを利用しました。

Spreadsheetの取得

該当箇所: session.spreadsheet_by_key(<スプレッドシートキー>)

GoogleDriveとかから見えるスプレッドシートの単位だと思います。キー以外にtitleとurlからも取得できます(参考)

WorkSheet取得

該当箇所: ws = session.spreadsheet_by_key(<スプレッドシートキー>).worksheets[0]

スプレッドシート内でのシート(画面左下の+ボタンで追加できるもの)のことのようです。サンプルでは0番目のシートを取得していますが、titleやgid(urlを見るとわかります)などから取得することも可能です(参考)

WorkSheetの操作

GoogleDrive/Worksheet

セルの値取得

例えば C2の値をとるならば c2 = ws[2, 3]とやれば取れます。簡単ですね。

セルの値変更

C2に"てくてくテック"と入れる場合は下記のようになります。

ws[2, 3] = "てくてくテック"
ws.save

最後の行や列の番号

ws.max_colsws.max_rowsで取得できます

行の挿入

#insert_rows(row_num, rows) ⇒ Objectを使います。row_num行目にrows行追加します。
e.g(ドキュメントより)

# Inserts 2 empty rows before row 3.
worksheet.insert_rows(3, 2)
# Inserts 2 rows with values before row 3.
worksheet.insert_rows(3, [["a, "b"], ["c, "d"]])

Reporterを使ってiTunes ConnectのSales and Trendsを自動で取得する - 設計編

iOS 技術

はじめに

Reporterを使ってiTunes ConnectのSales and Trendsを自動で取得するReporterを使ってみてAppleが提供するレポートファイルのダウンロードするところまでお話をしました。今回は、ダウンロードするだけでなく数値データを取得してどのように自動化するかという全体の設計についてまとめようと思います。

ゴール

今回作成する自動化機構の要件は下記の通りです。rubyなのは少し触ったことがあるからです。

  • デイリーのインストール数(アップデート数などは除く)を取得
  • 同一vendorに限定
  • iPhone, iPad両方の値を混合
  • 取得した値はGoogle Spreadsheetに出力
  • データ収集するアプリとその出力先は別途Google Spreadsheetで管理
  • デイリーでGoogle SpreadsheetからSlackにインストール数と合計インストール数を通知
  • ruby(2.3.1, rbenvにより管理)で実装

まとめると下図のようになります。

f:id:k-s-9190118:20161102110156p:plain

実装指針

Google SpreadsheetとRubyの連携

こちらはgoogel_driveというgemを使うと非常に簡単にできました。詳細は次回以降に書きます。

daily report

iTunes Connectから取得

これはjavaを実行することによってファイルをダウンロードします。この時、Appleアカウント IDやパスワードを記述したReporter.propertiesという設定ファイルが必要になるのですが、これらをファイルにベタ書きしておいておきたくないので、環境変数あたりに入れておいて実行時に一時的に作成&終わったら削除という実装にしようと思います。

インストール数の取得

reportファイルはtsv形式で出力されます。 データ項目は、 Provider, Provider Country, SKU, Title ...etcなど多岐にわたりますが、今回はSKUとProduct Type Identifier, Unitsを利用します。なお、SKUはiTunes Connectにアプリを作成するときに入力したものでBundle Identifierと同一にすることが多いので同一のものとしています。Product Type Identifierは製品タイプIDにあるように初回インストールなのかアップデートなのかの判断に利用できます(もちろんいわゆる製品タイプという区別もあります)。そしてUnitsがインストール数のことになります。

reportファイルの削除

めんどくさいことにapiでjsonを返してくれるとかいう設計じゃないreportファイルを毎日ダウンロードすることになるので、そのままだと不要なファイルがたまり続けることになります。そのため、プログラム実行後にはファイルを削除することにします。

Slackに通知する

SpreadsheetなのでGASを利用します。詳細はGASからIncoming Webhooksでslackに通知を投げるを参照してください。

終わりに

今回は設計や指針まで書きました。次回以降にそれぞれ細かい実装の話をしようと思います。

Swift3でのclosure

iOS Swift Swift3 技術

はじめに

Swift3にした時にclousureの部分の書き方が変わっていたなーというメモです

変更点

非同期の時は@escapingをつける

Swift2まではデフォルトが@escapingの状態で@noescapeにする際に明示的に@noescapeにするという文法だったのに、Swift3から逆転しました。 @esapingにするとどうなのか?という話はSwift 3 の @escaping とは何かがわかりやすいので、こちらを参照していただけたら良いと思います。

第一引数の前に _をつける

どうもclosureの第一引数の前に_をつけないといけないみたいです。
これはメソッドの第一引数にラベルを書くという文法の変更に近いお話です。この文法では従来のように第一引数にラベルをつけない場合は_をつけることになってます。Establish consistent label behavior across all parameters including first labelsの話です。Swift2では***With???みたいに第一引数のラベルのようなものをWithの後ろにくっつけるような書き方になっていたのを***(???)という感じにすっきりと書けるようになりました。 例えばSwift2では

func fileWithName(name: String){

}

こうだったのに対しSwift3ではこうなります。

func file(name: Strint){

}

//Swift2式で書く場合
func fileWithName(_ name: String){

}

メソッドの文法で第一引数にラベルをつけない場合は_をつけるのはわかったのですが、ではなぜclosureの第一引数では_をつけなければならないのでしょうか?
How do you document the parameters of a function's closure parameter in Swift 3?のstack over flowの回答のなかに

As @Arnaud pointed out, you can use _ to prevent having to use the parameter label when calling the closure:

In fact, this is the only valid approach in Swift 3 because parameter labels are no longer part of the type system (see SE-0111).

とありどうもRemove type system significance of function argument labelsが原因のようです。ここはまだちゃんと解読できていないのでそのうち読んだら追記します。

NavigationbarをHiddenじゃなくて透明にする

iOS Swift 技術

はじめに

NavigationbarをHiddenにして消すのではなく、タイトルやボタンは表示するがNavigationbarはあくまで透明としたい時があります(下図参照)。単純にBar TintをclearColorに変更するとなぜか黒くなります。今回は、下図のようにNavigationbarを透明にする方法について書きます。

https://gyazo.com/df36cbd4bb0c7186599a0fe51500e495

コード

透明にする時

self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()

戻す時

self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
self.navigationController?.navigationBar.shadowImage = nil

UIViewControllerのExtensionに入れておくといいかもですね。