続・MKMapSnapshotterでsnapshotが取れなくなる件 [楽しいプログラミング]
実機の方は前記事で一見問題が回避されたようだが、Simulatorの方ではさらに凶悪な事態になっていた。snapshotがブロックに送られないどころか、ブロックそのものが呼び出されないことがあるのである。これは酷い。
正直Simulatorなんかどうでも良いのだが(は?)、「simulatorで起きることは実機でも起きる」とは昔から言い習わされていることだ。(言い習わされてねーよ!)
とりあえずエラーハンドリングできないのが困る。当たり前のことだが、(あっちの)処理が終了しないから成功か失敗かの決着を付けることもできないのである。おまけに「処理中ですよ」的なぐるぐるを回していようものなら(UIActivityIndicatorViewのことです)、宇宙が消滅するまでそいつは回り続けることになる。
なのでタイムアウトで無理やり終わらせることにした。(笑)
class ViewController: UIViewController { @IBOutlet weak var mapView: MKMapView! @IBAction func snapshot(_ sender: Any) { let semaphore = DispatchSemaphore(value: 0) // セマフォ〜! let options = MKMapSnapshotter.Options() options.region = self.mapView.region options.size = CGSize(width: 640, height: 480) let snapshotter = MKMapSnapshotter(options: options) snapshotter.start { (snapshot, error) in // Snapshot!!! guard let snapshot = snapshot else { print("snapShotが出ーへん") semaphore.signal() // 終わりますた return } // ファイルを書き込む場所 let documentsPath = NSHomeDirectory() + "/Documents" let filename = "snapshot" var url = URL(fileURLWithPath: documentsPath) url.appendPathComponent(filename) url.appendPathExtension("png") print(url) // png dataを書き込む let data = snapshot.image.pngData() do {try data?.write(to: url)} catch {print(error)} semaphore.signal() // 終わりますた } // 10秒だけ待ってやる(Mainでwaitすると本処理が固まるのでサブスレにする) DispatchQueue.global().async { // 誰かがsignalする、または10秒経過するまでここ↓で待つ if semaphore.wait(timeout: DispatchTime.now() + 10) == .timedOut { //タイムアウトしたときにここに入る if snapshotter.isLoading { // 一応キャンセルしておく snapshotter.cancel() } print("snapShotが出ーへん") } } } }
ええんかいな?、、、こんなんで。(上のソースは雑すぎですw)
因みに初動で出力サイズ(options.size = CGSize(...の部分です)を小さめにするとたいていは通るが、画面サイズよりも大きいような出力サイズを設定するとこの現象が出る。そしてこれがまたさらに困ったことに、一度これが出ると何度やっても、出力サイズを小さくしようがどうしようが延々と出続けるのである。
アプリケーションを再起動した程度ではこの現象はなくならない。Simulationを再起動しなければならないのである。
どこかに何かのキャッシュが残っているのであろう。それが何かはわからない。自分のアプリフォルダ内の何だかよくわからないファイルを色々消してみたりしてみたが、事態は改善されない。そもそもDocumentsとTmp以外アプリケーション側で無闇やたらに消して良いものかどうか。
アプリケーションを再起動した程度ではこの現象はなくならない。Simulationを再起動しなければならないのである。
どこかに何かのキャッシュが残っているのであろう。それが何かはわからない。自分のアプリフォルダ内の何だかよくわからないファイルを色々消してみたりしてみたが、事態は改善されない。そもそもDocumentsとTmp以外アプリケーション側で無闇やたらに消して良いものかどうか。
仮に実機でもこの現象が起きた場合、デバイスを再起動していただくのが現状では最適な復旧手段なのだが、それについてどこかに「Appleのヴォケがわけわからん不具合出してくるから一応タイムアウトしておいたが、一度この現象が出ると繰り返し出てくるからデバイス再起動してな」などと書こうものなら速攻で落とされるに違いない。(笑)
さてどうしたものか。。。
もう一つ因みに、この現象は昔は出なかった。画面サイズの4x4倍くらいの出力でも大丈夫だったかと。
いつから出るようになったかはわからないが(ちゃんとテストしないからだ!)、直近で変わったことは、iOSのアップ(12です)と、この処理周りをswiftに書き換えたことぐらいか。(はい?)
今さら「セクシー・ユー」か何かをバックにノリノリでSwiftに書き換えたようなソースをObjective-Cに戻す気力はないので、確認するつもりもない。(無料アプリなので強気です)
いつから出るようになったかはわからないが(ちゃんとテストしないからだ!)、直近で変わったことは、iOSのアップ(12です)と、この処理周りをswiftに書き換えたことぐらいか。(はい?)
今さら「セクシー・ユー」か何かをバックにノリノリでSwiftに書き換えたようなソースをObjective-Cに戻す気力はないので、確認するつもりもない。(無料アプリなので強気です)
MKMapSnapshotterでsnapshotが取れなくなる件 [楽しいプログラミング]
唐突にプログラムネタで申し訳ない。
iOSのmapviewの画像を取り込みたい場合、通常はすなっぷしょったー(MKMapSnapshotter)というのを用いて、適当にオプションにパラメータ詰め込んで、こ〜んな感じにすれば良い。(雑な説明だなw)
class ViewController: UIViewController { @IBOutlet weak var mapView: MKMapView! @IBAction func snapshot(_ sender: Any) { let options = MKMapSnapshotter.Options() options.region = self.mapView.region //地図の範囲 options.size = CGSize(width: 640, height: 480) //出力画像のサイズ let snapshotter = MKMapSnapshotter(options: options) snapshotter.start { (snapshot, error) in // Snapshot!!! guard let snapshot = snapshot else {print("snapshotが出ーへん"); return} // ファイルを書き込む場所 let documentsPath = NSHomeDirectory() + "/Documents" let filename = "snapshot" var url = URL(fileURLWithPath: documentsPath) url.appendPathComponent(filename) url.appendPathExtension("png") // png dataを書き込む let data = snapshot.image.pngData() do {try data?.write(to: url)} catch {print(error)} } } }
ボタンを押したときのアクションに、上のsnapshot(_ sender: Any)を接続しておけば、Documentsディレクトリにpngファイルが書き込まれるという寸法だ。(何が寸法だよ)
ところが、このソースコードにおいて、snapshotter.startから戻ってきたブロック内で、まさに"snapshotが出ーへん"な現象が生じたのである。実に困る。その先が進まない。画像が出せない。
ってか何でブロックの1番目の引数(snapshotのことです)がOptionalなんだよ!(怒!) (nilで渡されることがある、つまりsnapshotオブジェクトが取得できない可能性があるという意味ですw)
いや笑い事じゃないぞ。私はこれで3日も悩んだのだ!(www)
ってか何でブロックの1番目の引数(snapshotのことです)がOptionalなんだよ!(怒!) (nilで渡されることがある、つまりsnapshotオブジェクトが取得できない可能性があるという意味ですw)
いや笑い事じゃないぞ。私はこれで3日も悩んだのだ!(www)
そうこうしているうちに、ふとiPhoneを見ると「設定」アイコンに赤丸がついている。急上昇中である。「容量いっぱいだからストレージ(有料)を増やしなさい」という、商売っ気まんまんのAppleさんのお告げである。
いつものことだ。藪だの山道だの藪だの風景だの藪だの鉄塔だのの画像で満タンになっている私のiPhoneのことである。(半分藪かよ!)
だがこれはこれで、まずいことはまずい。ひとまず藪だの山道だの(以下略)をPCに移動させ消去する。
よし、だいぶ空きができたぞ。赤丸も消えた。Appleも金をとりっぱぐれたことになる。ざまーみろ。(笑)
いつものことだ。藪だの山道だの藪だの風景だの藪だの鉄塔だのの画像で満タンになっている私のiPhoneのことである。(半分藪かよ!)
だがこれはこれで、まずいことはまずい。ひとまず藪だの山道だの(以下略)をPCに移動させ消去する。
よし、だいぶ空きができたぞ。赤丸も消えた。Appleも金をとりっぱぐれたことになる。ざまーみろ。(笑)
そんなわけで再起動とかかけて一晩置いておいたら、上のsnapshotがちゃんと出るようになった。(はい?)
どうやら、iPhoneの空き容量が少ないと、MKSnapshotterでsnapshotが取れなくなることがあるらしい。
以上のことに何の確たる証拠もないのだが(たまたまかもしれないし)、この現象について検索をかけても何も出てこなかったので、一応ここに書いておく。もし仮に同現象が出ている方がいらっしゃって、これで解決すれば良いのだが、違ってたらごめんなさい。
つづく(?)
サルにもわからないSwift講座 その1 Objective-CからSwiftに転向したときの「あるある」 [楽しいプログラミング]
プログラミング言語、「Swift」のお勉強中です。
プログラミングしながら気がついた役に立たないことをメモって行こうと思っています。
Objective-CからSwiftに転向したときの「あるある」
- 簡単なif文のときに{}を付けないでXcodeさんに速攻で怒られる
- ifの後に()を付ける。別に付けてもいいんだが、誰かに見られると恥ずかしいので(?)わざわざはずす。そして後でObjective-Cに戻ったときに今度はうっかり()を付け忘れる。
- for文も同様。switch文も同様
- 行末のセミコロンも同様
- 変数の定義で最初に型宣言しようとする→怒られる→「逆じゃん(てへっ笑)」とか言って変数から書こうとする→怒られる。しかも何で怒られているのかわからない。→あ、letやvar書き忘れた→てへっ
- とりあえず[を書いてから考える。(そんな文法はswiftにはないはず)
- ?や!がよくわからないので、Xcodeさんの「こう直しますか?」のお勧めの通りに順番に直していくと、そのうちどツボにはまって、Xcodeさんのお勧め機能もさじを投げてしまう。そして僕は途方にくれる。
- Objective-Cで造った共用できるモデルクラスの使い勝手にそこはかとなくイラっと来て、NSCodingやdescription含めて全部Swiftで書き直す。
- よせばいいのにIntとか無理にOptionalにして、あっちこっちで火の手が上がりドツボにはまる。
- 番外:NSDate→DateとかNSString→Stringとかはだいぶ慣れてきたが、いまだにUIImageをNSImageとか書いてしまう私は一体何者?
つづく。。。
UITableViewのセルが2倍に増える謎 [楽しいプログラミング]
さて、ここからが本題である。(何のだ?)
iOSアプリ制作において、またまたおかしな現象が起きた。今度はXcodeとかの設定の問題ではなく、純粋にプログラミングの問題である。何が原因かはわかったが、何が悪いのかはさっぱりわからない。まあ先日の「?」のように、カタギのプログラマであればまずやらないようなことなんだが、一応記録しておく。
例えばこういうテーブル、
これが、或〜る日突然、こうなっていたのだ。
以前、テレビの天気予報の表示で、気温が「99.9度」になっているのを観たことがある。うんうん、気持ちは良くわかるぞ。まさにそれだ。何でデフォが表示されている?
「或る日突然」というのはおそらくXcodeを8.0にアップデートした後である。その後こちらも小規模なバグを修正して更新の審査申請している。問題が起きているのはその更新バージョンである(よく審査通ったな(笑))
Storyboard上のプロトタイプセルは以下の通り。
reuseのIDとして上のセルに”switchCell”、下のセルにはただの"cell"と命名した。下のは他にも使いまわすスタンダードなものだから単純な名前で良いだろうと。これが後で死を招く。
さて、実装である。以下は正しい例、品行方正かどうかはわからないが、とりあえず修正後正しく動いたものである。
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
switch (indexPath.row) {
case 0:
{
cell = [tableView dequeueReusableCellWithIdentifier:@"switchCell" forIndexPath:indexPath];
UILabel * label = [cell viewWithTag:1];
label.text = @"あなたはぷにぷにですか?";
break;
}
case 1:
cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.textLabel.text = @"ぷにぷにのタイプは?";
cell.detailTextLabel.text = @"ものすごいぷに";
break;
}
return cell;
}
非の打ち所のない、ベタベタなテーブルの実装である。
さて、UITableViewControllerクラスはご存知のように、ファイルを新規で開くとデフォルトで何やらごちゃごちゃ書いてある。
上記該当部分に関してであれば例えばこんな感じ。
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#warning Incomplete implementation, return the number of sections
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
#warning Incomplete implementation, return the number of rows
return 0;
}
/*
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<#@"reuseIdentifier"#> forIndexPath:indexPath];
// Configure the cell...
return cell;
}
*/
最後のtableView:cellForRowAtIndexPath:にはコメント内にご丁寧にセルの取ってき方が書いてある。
私は当初、ここにデフォルトのセル(この場合"cell")を設定し、後で上書きすれば良いだろうと考えた。つまりはこんな感じである。
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
switch (indexPath.row) {
case 0:
{
cell = [tableView dequeueReusableCellWithIdentifier:@"switchCell" forIndexPath:indexPath];
UILabel * label = [cell viewWithTag:1];
label.text = @"あなたはぷにぷにですか?";
break;
}
case 1:
cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.textLabel.text = @"ぷにぷにのタイプは?";
cell.detailTextLabel.text = @"ものすごいぷに";
break;
}
return cell;
}
強参照で上書きならば何の問題もないと思ったのだが、これ、何かまずいのだろうか?何かやらかしているのだろうか?Xcode7の段階ではこれで問題なかった。申請前に何度もテストしたので間違いない。
ところが今現在これでビルドかけると、上で示したデフォルト「Title」祭りになる。
「self.tableView recursiveDescription」してみると、テーブル内の同じframeになぜかセルが2つづつ存在している。「Title」祭りと、ちゃんとtableView:cellForRowAtIndexPath:内でぷにぷにを設定したものとである。
これは謎だ。こちらのあずかり知らぬところでtableView:cellForRowAtIndexPath:がもう一回走っているのではないかと、ログやブレークポイント入れてみたが、その気配はない。
まあ何にせよ、tableView:cellForRowAtIndexPath:でリターンするUITableViewCellに関しては初期化しておくのが無難なようである。初期化しない、宣言しっぱなしというのも多分リスキーだろう。