10

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.

sometimesiwritecode
  • 2,993
  • 7
  • 31
  • 69

2 Answers2

17

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)
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Unfortunately on the SWIFT 4.0 you can't do that anymore, specially about the dynamic coordinate thing gives me an error Cannot override with a stored property 'coordinate' – JBarros35 Oct 04 '19 at 16:34
  • Note, I didn’t subclass an existing annotation type. I created my own. That eliminates the “override stored property” problem. But you don’t actually have to define your own type, if you don’t want, you just need to specify the concrete type for which `coordinate` is really a variable. `MKAnnotation` protocol only requires that `coordinate` exists, but not that it is a variable. But `MKPointAnnotation`, for example, declares it as a variable, so you can use that as is, if you want. I have updated my answer accordingly. – Rob Oct 04 '19 at 17:18
  • @JBarros35 - In Swift 5 at least, you can make coordinate a var (mutable) and set it. You just need to make sure that you cast MKAnnotation to your new class (let myAnnotation = mkAnnotation as? MyAnnotation) to be able to see and allow you to mutate. – BadPirate Dec 01 '21 at 16:34
  • That makes the annotation mutable, but if the respective annotation view is already shown on the map and you want the annotation view move as you change the annotation’s coordinate, you have to make that mutable coordinate `dynamic`, or else the annotation view won’t automatically move as the coordinate changes. Annotation views observe changes via KVO. – Rob Dec 01 '21 at 17:51
  • See https://github.com/robertmryan/DynamicAnnotations. – Rob Dec 01 '21 at 19:40
0

For Swift, you just need to make the coordinate field a var, and then type MKAnnotation when trying to mutate:

protocol MutableAnnotation : NSObject, MKAnnotation {
    dynamic var coordinate: CLLocationCoordinate2D { get set }
}

class MyAnnotation : MutableAnnotation {
    dynamic var coordinate: CLLocationCoordinate2D
}

// ...

func updateLocation(_ annotation: MKAnnotation, coordinate: CLLocationCoordinate2D)
{
    guard let mutable = annotation as? MutableAnnotation else { return }
    mutable.coordinate = coordinate
}

BadPirate
  • 25,802
  • 10
  • 92
  • 123