【Swift】MKMapView:ピンの中心表示からインセットをかけたい時の対処法

こんにちは。

さらにマップ操作にて知見が溜まったのでシェアしたいと思います。

今回は "setCenterとかあるけどinsetかけれないの?" という疑問を解決します。

中心地設定から位置をずらすには

Annotation(ピン)を選択した時や何かしらのタイミングでマップの中心に該当のAnnotationを表示すると思いますが、その場合は
setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool) を利用します。

ですが、マップの領域が比較的小さい場合でCallout(吹き出し)を表示する場合など、Annotationをマップの中心に設定してしまうとViewが見切れてしまう場合があります。

んーもうちょっと下に下げたり位置を調整したい、、insetとかその辺多分あるだろう、、

駄菓子菓子、ないのである。

そういった場合自前でうまいこと調整しないといけないようです。
ですがAnnotationはCoordinatesとしてマップ上に設定されており、調整したいのはデザイン上のPoint、、、
どうするかというと convert(_ point: CGPoint, toCoordinateFrom view: UIView?) -> CLLocationCoordinate2D を利用し、point → coordinate に変換してやります。

調整の仕方

やり方は以下のような感じで実現できます。

    func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
        // ignore UserLocation
        if view.annotation is MKUserLocation { return }

        // set a callout
        let calloutView = R.nib.placeCalloutView.firstView(owner: nil)!
        if let annotatioin = view.annotation as? PlaceAnnotation {
            calloutView.label.text = annotatioin.name
            calloutView.imageView.image = annotatioin.image
        }
        // make inset cuz the pin's centre is focused
        let inset = PlaceAnnotationView.height / 2
        calloutView.center = CGPoint(x: view.bounds.size.width / 2,
                                     y: (-calloutView.bounds.size.height / 2) - inset)
        view.addSubview(calloutView)

        // set inset to an annotation
        guard let annotation = view.annotation else { return }
        // cache the value to use as the start position for animation
        let currentCoordinates = mapView.centerCoordinate
        // set an annotation to the center
        mapView.centerCoordinate = annotation.coordinate
        // convert a position adjusted by inset/offset you want to move to coordinates on MapView
        let centerInset: CGFloat = 30
        let insetCenter = CGPoint(x: mapView.frame.width / 2,
                                 y: mapView.frame.height / 2 - centerInset)
        let coordinate = mapView.convert(insetCenter, toCoordinateFrom: mapView)
        // reset initial coordinates as the animation start position
        mapView.centerCoordinate = currentCoordinates
        // set the calculated coordinate
        self.mapView.setCenter(coordinate, animated: true)
    }

やってることとしては

  • 現在のマップ中心地を保持(最後にアニメーション開始位置として利用する)
  • 該当Annotationをマップの中心として設定
  • マップ中心=中心設定したAnnotationから動かしたいだけのインセットorオフセットを調整したCGPointを作成
  • これをマップ上のCoordinatesにconvertし、任意のcoordinatesを作成
  • 先ほどマップの中心地をAnnotationに変更したので、アニメーション開始位置である初期値に再設定
  • 作成したCoordinateをsetCenterして移動

という流れになります。
ちょっと回りくどいですが、現状のSDKだとこのくらいしか調整する方法がなさそうでした。
この convert()は他にも、マップ上でタップした場所に中心地を移動する、などといった場面でも活用する関数になるので、そういうケースになったら参考にしてみてください。

そうするとあらどーよ。

全体が見えるところまでインセットかけられましたね、よかったよかった。

終わりに

2本でMapViewは完結するはずでしたが、まだまだ奥が深いMapView。
SwiftUIを使った場合やGoogleMapSDKを利用した場合はやり方が変わってきそうですね。
またなんかあったら追記します、参考までに!