xyk blog

最近は iOS 開発の記事が多めです。

MKAnnotation ピンの画像をカスタマイズする

検証環境:
Xcode 12.4
Swift 5.3.2
iOS Deployment Target 14.4

マップ(MKMapView)上のピンの画像をデフォルトのものではなく別の画像にカスタマイズする方法について。

iOS11からマップビューにデフォルトでMKMarkerAnnotationViewが登録されているので、mapView.dequeueReusableAnnotationViewで取得し、glyphImageプロパティに画像を設定すればよい。
画像は iOS13 から追加された SF Symbols の画像を使って置き換えた。
またピンの色はmarkerTintColorを設定することで変更できる。

f:id:xyk:20210408132137p:plain

コードは前回のサンプルコードに追記していく形で。
xyk.hatenablog.com

サンプルコード

import UIKit
import MapKit

class ViewController: UIViewController {

    let mapView = MKMapView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(mapView)
        mapView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            mapView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            mapView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            mapView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor),
            mapView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor),
        ])
        
        mapView.delegate = self
        
        let center = CLLocationCoordinate2D(latitude: 35.68358493824179, longitude: 139.750327090532)
        mapView.region = MKCoordinateRegion(center: center, latitudinalMeters: 500, longitudinalMeters: 500)
        
        let longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress))
        longPress.minimumPressDuration = 0.5
        mapView.addGestureRecognizer(longPress)
    }
    
    @objc private func didLongPress(_ gesture: UILongPressGestureRecognizer) {
        guard gesture.state == .began else { return }
        
        let annotation = MKPointAnnotation()
        let location = gesture.location(in: mapView)
        let coordinate = mapView.convert(location, toCoordinateFrom: mapView)
        annotation.coordinate = coordinate
        annotation.title = "\(mapView.annotations.count)"
        mapView.addAnnotation(annotation)
    }
}

extension ViewController: MKMapViewDelegate {
    
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        
        let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier, for: annotation) as! MKMarkerAnnotationView
        annotationView.displayPriority = .required
        annotationView.glyphImage = UIImage(systemName: "flame")! // SF Symbols の画像を使用
        annotationView.markerTintColor = selectTintColor(annotation) // 色の変更
        return annotationView
    }
    
    private func selectTintColor(_ annotation: MKAnnotation?) -> UIColor? {
        guard let annotation = annotation as? MKPointAnnotation else { return nil }
        let colors: [UIColor] = [.systemRed, .systemBlue, .systemYellow, .systemGreen]
        let index = Int(annotation.title ?? "") ?? 0
        let remainder = index % colors.count
        return colors[remainder]
    }
}

MKMarkerAnnotationView を継承したカスタムクラスを用意した場合のサンプルコード

カスタムクラス内で画像や色を設定すれば MKMapViewDelegate の実装は不要。

import UIKit
import MapKit

class ViewController: UIViewController {

    let mapView = MKMapView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(mapView)
        mapView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            mapView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            mapView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            mapView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor),
            mapView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor),
        ])
        
        let center = CLLocationCoordinate2D(latitude: 35.68358493824179, longitude: 139.750327090532)
        mapView.region = MKCoordinateRegion(center: center, latitudinalMeters: 500, longitudinalMeters: 500)
        
        let longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress))
        longPress.minimumPressDuration = 0.5
        mapView.addGestureRecognizer(longPress)
        
        // カスタムアノテーションビューを登録
        mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
    }
    
    @objc private func didLongPress(_ gesture: UILongPressGestureRecognizer) {
        guard gesture.state == .began else { return }
        
        let annotation = MKPointAnnotation()
        let location = gesture.location(in: mapView)
        let coordinate = mapView.convert(location, toCoordinateFrom: mapView)
        annotation.coordinate = coordinate
        annotation.title = "\(mapView.annotations.count)"
        mapView.addAnnotation(annotation)
    }
}

// カスタムアノテーションビューの定義
class CustomAnnotationView: MKMarkerAnnotationView {
    
    override var annotation: MKAnnotation? {
        didSet {
            configure(for: annotation)
        }
    }
    
    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

        glyphImage = UIImage(systemName: "flame")!
        
        configure(for: annotation)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func configure(for annotation: MKAnnotation?) {
        displayPriority = .required
        markerTintColor = selectTintColor(annotation)
    }
    
    private func selectTintColor(_ annotation: MKAnnotation?) -> UIColor? {
        guard let annotation = annotation as? MKPointAnnotation else { return nil }
        let colors: [UIColor] = [.systemRed, .systemBlue, .systemYellow, .systemGreen]
        let index = Int(annotation.title ?? "") ?? 0
        let remainder = index % colors.count
        return colors[remainder]
    }
}