2017年12月28日木曜日

後からNavigationを追加する場合は

StoryboardでNavigationを使う場合、最初からならUINavigationControllerをドロップしてやればいいけど、ViewControllerに後から追加する場合、追加するのはUINavigationBarじゃなく、UINavigationItemの方だ。

2017年12月22日金曜日

MapViewの縮尺が思いどおりにならない

やりたいこと

地図をタップもしくはピンチして拡大/縮小した縮尺を覚えさせ、次回表示時に同じ縮尺で表示したい。
複数の元データがあり、地図を開くごとにそれぞれ個別の縮尺で表示したい。

やったこと

mapViewの.region.span.latitudeDelta、.region.span.longitudeDeltaの値が地図の縮尺の値(正確には表示範囲の緯度、経度の角度)なので、地図を閉じる際にそれを保存し、開く際に同じ値を設定してやる。

うまくいかないこと

表示されたものがだいぶズームアウトしたものになってしまう。

たとえば地図に以下の値を設定したのに、
latitudeDelta: 0.047710965458634291, longitudeDelta: 0.10232551591900574
地図表示後は以下の値になってしまう🤔
latitudeDelta: 0.19593365632808712, longitudeDelta: 0.42023935281952163

再度地図を開閉するとその値を保存するので、開閉するたびにどんどんズームアウトしてしまう。
最初これはiPad、iPhoneシミュレータ(iPadの擬似iPhone表示含む)で起こった。
そのうち、コードやらConstraintsやらをいじっていたところ、iPadで現象が起こらなくなった。そのかわりiPhoneで今度はズームインするようになった。

原因を推測

アップルのドキュメントによると、設定した縮尺はそのまま利用されるのでなく、指定された領域全体が画面に収まるように、勝手に少しずつズームアウトするらしい。
regionプロパティに割り当てる値(またはsetRegion:animated:メソッドで設定する値)は通常、このプロパティによって最終的に保存される値と同じではありません。領域のスパンを設定すると、表示したい矩形が名目的に定義されるだけでなく、Map View自体の拡大縮小レベルも暗黙的に設定されます。Map Viewは、任意の拡大縮小レベルを表示することができないため、指定された領域を、Map Viewがサポートする拡大縮小レベルに合うように調節しなければなりません。Map Viewは、できる限り画面いっぱいに表示しながら、指定した可視全体を表示できる拡大縮小レベルを選択します。その後、それに応じてregionプロパティを調節します。regionプロパティの値を実際には変更せずに結果の領域を確認するには、Map ViewのregionThatFits:メソッドを使用できます。
毎回値が固定されてればいいけど、ズームアウトされた値を保存して次回使うようにすると、使うたびにどんどんズームアウトされちゃうんだな。

どのタイミングで値が変わる?

どのタイミングで値が変わるのかを調べるため、実行時に順に呼ばれるメソッドを監視したところ、以下の順で呼ばれていることがわかった。
ちなみにviewWillAppearの中から地図の設定のメソッドを呼び出して緯度経度および縮尺を設定している。
  1. viewDidLoad
  2. viewWillAppear←この中で縮尺等設定
  3. mapViewWillStartRenderingMap
  4. mapViewWillStartLoadingMap
  5. mapViewDidFinishLoadingMap
  6. viewDidAppear
  7. mapViewDidFinishRenderingMap
  8. mapViewWillStartLoadingMap
  9. mapViewDidFinishLoadingMap

それぞれの時点での縮尺の値の変化


viewDidLoad 
latitudeDelta: 0.19593365632808712, longitudeDelta: 0.42023935281952163

viewWillAppearから呼び出すメソッド内でmapViewに値を設定。
設定直前 
latitudeDelta: 0.19593365632808712, longitudeDelta: 0.42023935281952163

(ここでmapViewに前回保存したregionごと設定
mapView.region = prev.region!)

設定直後 
latitudeDelta: 0.33684171745417757, longitudeDelta: 0.42023935281952163 
↑なぜか片方だけ値が勝手に変わっている

viewWillAppearでマップに関係ない他の処理をした最後 
latitudeDelta: 0.19593365632808712, longitudeDelta: 0.42023935281952163 
↑なぜか値が正常値に戻る

mapViewWillStartRenderingMap 
latitudeDelta: 0.80467494493102976, longitudeDelta: 1.7258762895046402
↑ここでとうとう値が大きく変わり、以下そのまま

mapViewWillStartLoadingMap 
latitudeDelta: 0.80467494493102976, longitudeDelta: 1.7258762895046402

mapViewDidFinishLoadingMap 
latitudeDelta: 0.80467494493102976, longitudeDelta: 1.7258762895046402

viewDidAppear 
latitudeDelta: 0.80467494493102976, longitudeDelta: 1.7258762895046402

mapViewDidFinishRenderingMap 
latitudeDelta: 0.80467494493102976, longitudeDelta: 1.7258762895046402

mapViewWillStartLoadingMap 
latitudeDelta: 0.80467494493102976, longitudeDelta: 1.7258762895046402

mapViewDidFinishLoadingMap 
latitudeDelta: 0.80467494493102976, longitudeDelta: 1.7258762895046402

対処方法

  • 縮尺を固定する
    • 前回の縮尺を利用っていう希望は実現されないけど、いちばん楽。
  • 表示時か保存時に値をちょっとずつズームインさせて、調整分を吸収する
    • どれくらいズームインするのかわからないので、結局使っているうちに少しずつズームアウトされたり、ズームインしすぎたりしてしまう恐れがある。
    • 必要なズームインの比率がデバイスの違いや、マップサイズの変更、iOSの仕様変更などで細かく変わるかもしれない。
やっぱり縮尺をある程度固定しておくのがいいかねえ?