見出し画像

Apple Mapチュートリアル - 第3回:マップの操作

前回、自分の位置情報を取得するところまで解説しました。

この回では、まず地図を自分の位置まで移動させる(地図を自分の位置にフォーカスさせる)ことから始めたいと思います。

前回作ったupdateMapメソッドは、位置情報をprintしただけでした。

// MARK: - Map
   private func updateMap(currentLocation: CLLocation){
       print("Location:\(currentLocation.coordinate.latitude), \(currentLocation.coordinate.longitude)")
   }

ここに、自分の位置へ地図をフォーカスさせる実装を追加していきます。

地図をある地点にフォーカスさせるには、MKMapViewのsetRegionという関数を使います。ここに表示させたいマップのRegion(エリア)をしていすることでマップのフォーカスを変えることができるのです。

リージョンは、下のようにセンターの位置情報と、緯度(縦)方向の距離、経度(横)方向の処理を指定して作ります。

MKCoordinateRegion(center: センターの位置,
latitudinalMeters: 縦方向の距離(メートル),
longitudinalMeters: 横方向の距離(メートル)

今回は横方向(経度)の距離を5000メートルとして、その後、MapViewの画面サイズを図り、縦横のアスペクト比を求めてから、縦方向(緯度)の距離を求めます。

コードはこのようになりました。

// MARK: - Map
private func updateMap(currentLocation: CLLocation){
   print("Location:\(currentLocation.coordinate.latitude), \(currentLocation.coordinate.longitude)")

   let horizontalRegionInMeters: Double = 5000

   let width = self.mapView.frame.width
   let height = self.mapView.frame.height

   let verticalRegionInMeters = Double(height / width * CGFloat(horizontalRegionInMeters))

   let region:MKCoordinateRegion = MKCoordinateRegion(center: currentLocation.coordinate,
                                                      latitudinalMeters: verticalRegionInMeters,
                                                      longitudinalMeters: horizontalRegionInMeters)

   mapView.setRegion(region, animated: true)

}

ここまでで一度コードを実行してみてください。下のようになっていれば成功です。スカイツリーの場所を起点に、横方向5000メートルの幅でマップを表示できました。

スクリーンショット 2020-07-26 12.08.25

ここで、自分の位置(この場合スカイツリーの場所)に何もアイコンが表示されていないのがわかります。

MKMapViewはデフォルトではユーザーの位置にアイコンを置くようになっていないので、これを変更します。
Storyboardで、MKMapViewを選択し、右のMap Viewの設定のところにある、User Locationというチャックマークをチェックしてください。

スクリーンショット 2020-07-26 12.15.18

これで下のように、マップの中央に自分の位置を表示することができました。

スクリーンショット 2020-07-26 12.11.43

おまけ

今回のコードをテストしていて、シミュレーターのFeatures > Location > Custom Locationから新しい位置情報を入力して、アプリを再起動しても、前の位置情報のところにマップのフォーカスが行ってしまうことを体験された方もいるかもしれません。

これは、CLLocationManagerが前回取得した位置情報をキャッシュし、didUpdateLocationに渡してくるからです。

これは、「位置情報が取りづらいとき、とりあえずでも最後に取れた位置情報をくれる」という便利な機能なのですが、毎回フレッシュな位置情報を使って挙動を試したい時は邪魔になります。

この対処法を2つほど紹介します。

1つ目は、まず、stopUpdatingLocation()をコメントアウトしてしまうことです。

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
       if let location = locations.first {
           //locationManager.stopUpdatingLocation()
           updateMap(currentLocation: location)
       }
   }

こうすることで、didUpdateLocationが何度も呼ばれますので、新しい位置情報を使ってマップのフォーカスの挙動を見ることができます。
stopUpdatingLocation()を呼ばないと位置情報をアプリがとり続けてしまうので、viewWillDisapperなどに、このstopUpdatingLocation()を移動しておいたほうがいいでしょう。

この方法ですと、例えばマップをスクロールして他のエリアをゆっくり見たいときに自分の位置情報が更新されると毎回自分の位置にマップが戻されてしまいます。

フレッシュな位置情報が一度だけ欲しい場合はもっといい方法があります。
CLLocationオブジェクトには、timestampという、位置情報を取得したときの日時がDateクラスのオブジェクトで入っています。これを現在の時刻と比べ、古かったら使わない(= stopUpdatingLocation()も呼ばない)ようにすることで、新しい位置情報がくるまで待つことができます。

private func updateMap(currentLocation: CLLocation){
   print("Location:\(currentLocation.coordinate.latitude), \(currentLocation.coordinate.longitude)")

   let now = Date()
   let delta = now.timeIntervalSince(currentLocation.timestamp)
   print("This location was obtained \(delta) seconds ago")
   if delta > 60{
       print("This location is too old")
       return
   }
 

上のように、timeIntervalSince関数をつかって、currentLocationのtimestampから現在まで何秒経っているかを測ります。
この例では60秒以上経っていたらreturnするようにしています。この60秒という閾値は自分の目的に応じて変えてみてください。

まとめ

お疲れ様でした。今回は、

マップに表示させたいリージョンの計算方法
ユーザーの現在地にアイコンを表示させる方法
古い位置情報を使用しない方法

を紹介しました。次回はマップにピンを表示させる方法を紹介したいと思います。

このチュートリアルのソースコードは↓↓↓に置いてあります。
(GitHub上で☆をつけていただくと励みになります!)

https://github.com/mizutori/iOSMapStarterKit

ここまでのコードは、コミットハッシュ
36ba4b23dd4eb437de7fa76951fec94691bcab20
でコミットしてあります。下のコマンドでロールバックして見てみてください。

git checkout 36ba4b23dd4eb437de7fa76951fec94691bcab20

このブログに関する質問やiOSアプリの開発の相談はこちらから↓↓↓

@mizutory
mizutori@goldrushcomputing.com

次回はマップにピンを表示します↓↓↓




この記事が気に入ったらサポートをしてみませんか?