課題5完成 - GCD利用&マップ表示

CS193pのLecture11の課題5ができました。

時間のかかる処理はGCDで別スレッドで行うようにしたのでカクカク感がなくなり、快適になりました。

iPhone

マップ表示すると見たい都市が一発で選べて便利ですね。

待機中を示す歯車アイコンは画像表示領域の中央に表示するようにしました。

iPad

iPadだとマップを見ながら写真を見ることが出来るので、この場所で撮られた写真という場所のイメージできて分かりやすいです。

直近に見た写真の一覧を表示する部分ですが、GCDを使って別スレッドでサムネイル画像を取得するようにしました。すると、画面が固まることがなくなって快適になりました。

サムネイル画像が取得出来た行は、右から左にスライドして表示されるようにしています。

分担して処理してるというのが分かって楽しいです。

 

はまったこと

キャッシュの場所

最初はキャッシュ用にファイルを作成する場所がよく分かりませんでした。

ヒントにアプリのサンドボックス内と書いてあるのでそのようなことが書いているiOSのドキュメントを探して、このドキュメントを見つけました。

File System Programing Guide

これにこんな絵が貼ってあります。

どうやら、図のAppというディレクトリがアプリ用のディレクトリでアプリのサンドボックスのようです。その配下のディレクトリの説明を見ると、キャッシュとして使用してよいのは次のディレクトリのようです。

<アプリのサンドボックス>/Library/Caches

Libraryディレクトリは、データキャッシュファイルやアプリが簡単に再作成するファイルを保存するディレクトリ。

Library配下のファイルはiTunesにバックアップされるがCachesはバックアップされない。

ということで、NSFilemanagerのインスタンスにNSCachesDirectoryというオプションを渡してディレクトリを取得し、ドキュメントのサンプルプログラムにあるようにmainBundleのidを追加して以下のディレクトリをキャッシュに使うようにしました。

<アプリのサンドボックス>/Library/Caches/<メインバンドルid>

これで一件落着かと思ったら、このディレクトリでキャッシュ用のファイルが作成/削除される様子を見ていると…

Cache.dbという身に覚えのないファイルが出来ています。

しかも、画像のキャッシュファイルを作成するたびに、そのサイズ分ほどサイズが大きくなっていきます。気持ち悪い…

恐る恐るテキストエディタでそのファイルをのぞいてみると、なんとFlikrから取得したdictionaryデータやら画像データと思われるバイナリデータやらが入っていました。

その正体は定かではありませんが、入っているデータとそのファイル名から想像すると、http://〜で取得したデータをローカルキャッシュとして保存しているファイルのようです。

このままでは今回作成するアプリによって、制限10MBを超えたらこのファイルは削除されてしまいます。

そういうわけで、結局、このファイルを削除しないようにするために1階層下げて次のディレクトリをキャッシュに使うことにしました。

<アプリのサンドボックス>/Library/Caches/<メインバンドルid>/photoImage

歯車アイコン

デモではコードで1から待機中を示す歯車アイコンを作成していましたが、オブジェクトライブラリにActivity Indicator Viewという名前でありました。そして、"Hides When Stopped"プロパティで自動的に消すこともできました。実装するコードは、このアウトレットにアニメーションのスタート/ストップメッセージを送るだけです。

しかし、忘れてはいけないのが struts and springs の設定です。回転させた時に変な位置に移動するので少し悩みましたが、次のように設定すると、センターキープできました。

マップのスケール

表示するマップの範囲を次のメソッドで指定したのですが、regionの中のセンター位置は良いとして、spanの意味が分かりません。

- (void)setRegion:(MKCoordinateRegion)region

animated:(BOOL)animated;

@property MKCoordinateRegion region;

typedef struct {

CLLocationCoordinate2D center;

MKCoordinateSpan span;

} MKCoordinateRegion;

typedef struct {

CLLocationDegrees latitudeDelta;

CLLocationDegrees longitudeDelta;

}MKCoordinateSpan;

 

ドキュメントを見ると、度の単位で指定して1°がだいたい111kmということです。

表示領域の範囲をこれで指定して、小さい値を設定するほど拡大率が上がるようです。

そこで、表示する全ての場所の緯度と経度から次のようにregionを計算しました。

center.latitude=MIN(緯度…) + ( MAX(緯度…) − MIN(緯度…) )/2

center.longitude=MIN(経度…) + ( MAX(経度…) − MIN(経度…) )/2

span.latitudeDelta=MAX(緯度…) − MIN(緯度…)

span.longitudeDelta=MAX(経度…) − MIN(経度…)

そして、全ての注釈が画面にフィットして、なおかつ美しく見えるように余白を取るために、spanのそれぞれの値を1.2倍して次のように実装しました。

    [self.mapView setRegion:self.region animated:YES];
    [self.mapView regionThatFits:self.region];  //アスペクト比を画面にフィットさせる

■spanに大きすぎる値を設定するとクラッシュ

これで動かしてみると、都市の中の写真スポットの表示は雰囲気上手くいっているのですが、都市をマップ表示する時にクラッシュします。

エラーログを見てみると、どうやらspanに大きすぎる値を渡すとクラッシュするようです。

そこで、世界地図に都市を表示する時は、spanを1.2倍せずにそのまま表示することにしました。

■region設定は-(void)viewWillAppearで

最初viewDidLoadで上記のregion設定をしていたのですが、画面からもれるスポットがあって、どうもregion設定が上手く出来てない感じで悩みましたが、-(void)viewWillAppearで設定すると期待どおりに動きました。

別スレッド処理時の制約

全てのネット越しの処理とキャッシュのファイル処理をGCDで別スレッドにディスパッチして行うようにしたところ、都市一覧画面>写真一覧>マップと間髪入れずに画面遷移させるとマップに注釈が表示されませんでした。マップ表示するためのデータがまだ取得出来ていない状態でマップ画面を表示させたせいです。そこで、データ取得出来ていない状態ではマップ画面に移動出来ないようにmapボタンをdisableにして、データ取得が完了したらenableにするようにしました。

GCD利用時にメモリ利用を最小にする方法

ローカル変数はstrongポインタなので、普通にローカル変数を使用してキューにディスパッチすると、mainキューの中で表示待ちになっている数分のバッファが確保された状態になります。そこで、サイズの大きな画像データ用のバッファは画像表示用画面のViewControllerのプロパティとして定義してこれを利用するようにしました。これでどんなにたくさんディパッチされてもViewControllerのインスタンスは1つしかないので、バッファは1つ分しか存在せず、メモリの使用量を抑えることが出来ます。

このことに気が付いて嬉しくなって、直近の閲覧履歴のリストにサムネイル画像を表示する部分もViewControllerのプロパティを使って画像データを取得するようにして別スレッドのキューにディスパッチするようにしました。すると、本来の画像とは異なったサムネイル画像が表示されるようになりました。最大20件分のサムネイル画像を一気に別々のキューで同時に取得するので、サムネイル画像を取得するキーと画像バッファが一致しない状態になったようです。テーブルビューの各行に表示するサムネイル画像はその数分だけ必要なので、こちらは普通にローカル変数でメモリ確保した方がよさそうです。

そして、キーとサムネイル画像は完全に一致するようにdictionaryに格納して、dictionaryに存在しないときだけ画像取得するようにしました。

iPad storyboard

マップ用のView Controllerは1つで共用しているのですが、storyboard上も1つでsegue接続するとコンパイラのwarnningがでてクラッシュしたので、同じものをコピー&ペーストして2つにして接続せさました。

写真表示画面の上部のツールバーは、普通のツールバーだと表示位置の調整が面倒で見栄えもなぜか微妙に位置ずれした感じになったので、Navigation View Controllerをかませてそのナビゲーションバーを使いました。

<前の記事

CP193p 課題5の内容   - CS193p - Lecture 12 次の記事>