MKMapSnapshotterを使ってCell内に地図を表示しピンを立てる

こんにちは。
今日はハロウィンということで、美容室に行ったらお菓子をいただきました(ヤッター)🍭🎃

最近MKMapSnapshotterについて調べることがあったので、今回はその備忘録も兼ねて記事を書いてみます。 MKMapSnapshotterはMapKit内に用意されたクラスで、地図のキャプチャ画像を生成できる仕組みです。

作りたいもの

今回は、以下のようにCollectionViewのCellの中に地図を表示し、タップしたら地図画面に遷移させる、という処理を実装してみました。 (サンプルコードにて↓の実装を公開しています。)

f:id:muchan611:20201031143221p:plain:w250 f:id:muchan611:20201031143246p:plain:w250

実は、CollectionViewやTableViewのCellに地図を表示したい際に、MKMapViewをそのまま使ってしまうと、動作がとても重くなってしまいます。ひどいと画面が固まったかのように見えます 💭💭
そこで、地図をキャプチャして画像にしてくれるMKMapSnapshotterを使うと、Viewは画像を表示するだけでよくなりスムーズに描画できるようになるのです。

MKMapSnapshotterの使い方

MKMapSnapshotterを使って画像を取得する実装はとても簡単で、座標や画像のサイズなどのオプションを指定しMKMapSnapshotterから得た画像を対象のUIImageViewにセットするだけです。

let location = CLLocationCoordinate2D(latitude: item.lat, longitude: item.lon)
let span = MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)
let options = MKMapSnapshotter.Options()
options.size = CGSize(width: 400, height: 200)
options.region = MKCoordinateRegion(center: location, span: span)
options.scale = UIScreen.main.scale
options.mapType = .standard

let snapshotter = MKMapSnapshotter(options: options)
snapshotter.start(completionHandler: { [weak self] (snapshot, _) in
    self?.mapImageView.image = snapshot?.image
})

今回はその中心にピン画像も表示したかったので、MKPinAnnotationViewを使ってピンを表示する実装も追加しました。 サンプルコードはこのあたり

snapshotter.start(completionHandler: { [weak self] (snapshot, _) in
    guard let snapshot = snapshot else { return }
    
    let image = UIGraphicsImageRenderer(size: options.size).image { _ in
        snapshot.image.draw(at: .zero)

        let pinView = MKPinAnnotationView(annotation: nil, reuseIdentifier: nil)
        let pinImage = UIImage(named: "pin")!
        pinView.image = pinImage

        var point = snapshot.point(for: location)
        point.x -= pinView.bounds.width / 2
        point.y -= pinView.bounds.height / 2
        point.x += pinView.centerOffset.x
        point.y += pinView.centerOffset.y
        pinImage.draw(at: point)
    }

    self?.mapImageView.image = image
})

MKMapViewで使うMKAnnotationViewの画像を変える方法

画面全体に地図を表示する実装もとても簡単でした。サンプルコードはこちら。 ピンをオリジナルの画像にしたい場合は、MKMapViewDelegateの mapView(_:viewFor:) 内で表示したい画像に差し替える必要がありそうでした。

extension MapKitSampleMapViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        let myAnnotation = MKAnnotationView(annotation: annotation, reuseIdentifier: "pin")
        myAnnotation.image = UIImage(named: "pin")!
        myAnnotation.annotation = annotation
        return myAnnotation
    }
}



MapBoxを使った似たような実装を、プライベートで開発しているYamarii(ヤマリー)の一部画面で取り入れています。 良かったら覗いてみてください〜 👀 😊