見出し画像

Apple Mapチュートリアル - 第4回:マップにピンを表示しよう

前回までのチュートリアルで、自分の位置をマップに表示することができるようになりました。

今回は、マップの上にピンを立てる方法を説明します。

今回は、AppleのMKLocalSearchというクラスを使って自分の位置にある施設・お店などを検索してピンで表示するという機能を作って見たいと思います。

まず、searchedMapItemsというArrayをViewController.swiftに定義しましす。

var searchedMapItems:[MKMapItem] = []

(今回の実装はすべてViewController.swiftの中で行います)
下のようにupdateMapのなかのsetRegionのあとに、searchNearby()を呼び出して、近隣のカフェを検索するようにします。

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

   mapView.setRegion(region, animated: false)
   searchNearby(region: region)
}

(関数コールが深くなっていくという指摘も出てきそうですが、前回コードからの変更を極力少なくするために今回はこうしました💦)

searchNearByのなかでは、MKLocationSearch.Requestクラスのオブジェクトをつくります。これに、検索のリクエスト(要求)をセットしていきます。searchNearByの中の2行目で"coffee"というクエリを設定しています。
これで、近隣のお店から"coffee"に関連する場所を探すようにリクエストにセットしています。
次の行で、regionに、現在mapViewが表示しているregionをそのままセットします。これで、「今マップ上に見えているエリアを探してください」というリクエストがセットされます。

private func searchNearby(region: MKCoordinateRegion) {
   let request = MKLocalSearch.Request()
   request.naturalLanguageQuery = "coffee"
   request.region = mapView.region
   let search = MKLocalSearch(request: request)
   search.start { [weak self] response, _ in
       guard let response = response else {
           return
       }
       self?.searchedMapItems = response.mapItems
       self?.showPins()
   }
}

次にこのrequestオブジェクトをつかって、MKLocationSearchというオブジェクトを生成します。このsearchというオブジェクトのstartというメソッドを呼び出して、検索を実行します。
検索が終わると、クロージャーのなかで結果をresponseというオブジェクトで受け取ることができます。このresponseの中のmapItemsにヒットしたコーヒに関連するお店のリストが入っています。
これをsearchedMapItemsという変数に代入します。そして、このあとにピンを表示するための関数showPins()をコールします。

ピンの表示

ピンの表示はshowPins()というメソッドに実装しました。

    private func showPins(){
       mapView.removeAnnotations(mapView.annotations)

       for item in self.searchedMapItems{
           let placemark = item.placemark
           let annotation = MKPointAnnotation()
           annotation.coordinate = placemark.coordinate
           annotation.title = placemark.name
           if let city = placemark.locality,
           let state = placemark.administrativeArea {
               annotation.subtitle = "\(city) \(state)"
           }
           mapView.addAnnotation(annotation)
       }

   }

このメソッドの中では、まず、mapViewのremoveAnnotationsを呼んで、今表示されているピンをすべて消します。その後searchedMapItemsをの中身を1つずつ取り出して、placemarkというオブジェクトを取り出します。この中に、位置(coordinate)、名前(name)が入っているので、それをMKPointAnnotaionクラスのオブジェクト(annotation)のcoordinatetitleにそれぞれ設定します。
最後にannotationオブジェクトを、mapView.addAnnotation()メソッドを使って登録します。これで、ピン(annotationオブジェクト)がマップ上に表示されます。
annotationオブジェクトがマップ上に表示されるピンの実態であり、ピンは、annotationオブジェクトのcoordinate変数に登録した場所に表示されるようになります。

スクリーンショット 2020-07-27 9.32.28

次は、このデフォルトのピンをカスタムのものに変えてみたいと思います。

カスタムのピンを表示する

マップ表示機能を作っていると、ピンを自分独自のものに変えたくなることがよくあります。今回は下の画像にピンを変えてみましょう。

画像4

まず、下のように、ViewControllerの最初でMKMapViewDelegateプロトコルをImplement(実装)する宣言をします。

class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {

つぎに、viewDidLoad内で、mapView.delegateにselfを登録します。

    override func viewDidLoad() {
       super.viewDidLoad()

       locationManager = CLLocationManager()
       locationManager.delegate = self
       locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters

       mapView.delegate = self
   }

これで、MKMapViewのデレゲートメソッドを受け取ることができます。(MapViewでなんかあった時のイベントをとれるようになる)

つぎに、MapViewがピン用のViewを要求してくるデレゲートメソッドがあるのでそれを実装します。

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
       
       let identifier = "MyPin"
       var annotationView: MKAnnotationView!

       if annotationView == nil {
           annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
       }

       // pinに表示する画像を指定
       annotationView.image = UIImage(named: "pin")!
       annotationView.annotation = annotation
       annotationView.canShowCallout = true

       return annotationView
   }

この、mapView viewFor annotationメソッド内で、MKAnnotionViewを作って返して上げることで、マップは、このメソッドで返されたViewをピンとして表示するようになります。
annotationView.image = UIImage(named: "pin")!
の箇所で、ピンに先程の画像を登録しています。

実行すると、先程のピンがカスタムのピンになりました!

スクリーンショット 2020-07-27 9.30.20

ピンをタップすると、先程のshowPins()のなかでMKPinAnnotationに設定したtitlesubtitleが表示されるのもわかると思います。
これで完成です!といいたいところなのですが、、
この状態で画面中央のピンをタップしてみてください。下のように、My Locationと表示されます。ん???、これはなんでしょうか?

スクリーンショット 2020-07-27 9.30.23

mapView viewFor annotationメソッドを実装したことで、自分の位置のアイコン(青い丸の)もカスタムされてしまいました。

この対策として、下のように、mapView viewFor annotationメソッドひ引数として渡されるannotationオブジェクトが自分かどうかを判定して、自分だったらパスをするという1行のコードを入れると、解決できます。

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
   //自分のアイコンだったらパスする
   guard annotation as? MKUserLocation != mapView.userLocation else { return nil }

   let identifier = "MyPin"
   var annotationView: MKAnnotationView!

   if annotationView == nil {
       annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
   }

これで、自分の位置はブルーのアイコンのままで、ピンをカスタムすることができました。

スクリーンショット 2020-11-22 18.10.27

おまけ

今回は、自分の位置のアイコンだったらnilを返してパスするようにしましたが、

//自分のアイコンだったらパスする
guard annotation as? MKUserLocation != mapView.userLocation else { return nil } 

ここで、例えば別のアイコン画像をつかったMKAnnotationViewを返すようにすれば、自分の位置のアイコンもカスタムすることができます。
これも是非、試して見てください。


まとめ

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

MKLocationSearchを使って近隣のカフェを"coffee"のキーワードで検索する
検索にヒットしたお店の位置にピンを表示する
ピンの画像を自分の好きな画像にカスタムする
自分の位置までカスタム画像になってしまうことを回避する

これらの方法を紹介しました。

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

https://github.com/mizutori/iOSMapStarterKit

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

git checkout ecaa82b8e9efce926b36a6986f96f663be1bb46e


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

@mizutory
mizutori@goldrushcomputing.com

次回は、ユーザーのマップの操作に合わせたインタラクションを実装する方法を紹介します!

いいなと思ったら応援しよう!