Search code examples
iosswiftmkannotationcllocationapple-maps

How do I update the location of an MKAnnotation object on a mapView?


I have the following code, which aims to loop through all the annotations on the current apple map view and update their location coordinates.

for existingMarker in self.mapView.annotations {

    existingMarker.coordinate.latitude = 12.12121212
    existingMarker.coordinate.longitude = 14.312121121          
}

Sadly this is not allowed. I am told that 'coordinate' is a get only property. So this is obviously not how I am meant to update the MKAnnotation's location of annotations already drawn on a mapView. How can I do this then? Specifically I would like to do this and have the map "redraw" with the new coordinates ASAP. I am sure this must be possible as it seems like a common use case.


Solution

  • The issue is that annotations is an array of MKAnnotation. But this protocol only requires that there is a coordinate property, but doesn’t dictate that it is a variable. Note the absence of set in the protocol:

    public protocol MKAnnotation : NSObjectProtocol {
    
        // Center latitude and longitude of the annotation view.
        // The implementation of this property must be KVO compliant.
        var coordinate: CLLocationCoordinate2D { get }
    
        ...
    }
    

    So, when iterating through the annotations of the MKMapView, which is defined as an array of MKAnnotation, it doesn’t know that your coordinate is a variable or a constant, and generates that warning.

    But, let’s imagine that your annotations were MKPointAnnotation. In that concrete annotation type, the coordinate is a variable, not a constant. So you can be specific about the type. For example:

    for annotation in mapView.annotations {
        if let annotation = annotation as? MKPointAnnotation {
            annotation.coordinate = CLLocationCoordinate2D(latitude: 12.12121212, longitude: 14.312121121)
        }
    }
    

    Or

    mapView.annotations
        .compactMap { $0 as? MKPointAnnotation }
        .forEach { existingMarker in
            existingMarker.coordinate = CLLocationCoordinate2D(latitude: 12.12121212, longitude: 14.312121121)
    }
    

    Obviously, you if you define your own annotation class that conforms to MKAnnotation, obviously:

    • define coordinate as a variable, not a constant; and

    • make sure it’s dynamic.

    Thus:

    class MyAnnotation: NSObject, MKAnnotation {
        dynamic var coordinate: CLLocationCoordinate2D
        dynamic var title: String?
        dynamic var subtitle: String?
    
        // other properties unique to your annotation here
    
        init(coordinate: CLLocationCoordinate2D, title: String? = nil, subtitle: String? = nil) {
            self.coordinate = coordinate
            self.title = title
            self.subtitle = subtitle
    
            super.init()
        }
    }
    

    And then the pattern is the same as above, except reference your class, e.g.:

    mapView.annotations
        .compactMap { $0 as? MyAnnotation }
        .forEach { existingMarker in
            existingMarker.coordinate = CLLocationCoordinate2D(latitude: 12.12121212, longitude: 14.312121121)
    }