The Maps app in iOS 10 now includes a heading direction arrow on top of the MKUserLocation MKAnnotationView. Is there some way I can add this to
I wonder why no one offered a delegate solution. It does not rely on MKUserLocation but rather uses the approach proposed by @Dim_ov for the most part i.e. subclassing both MKPointAnnotation and MKAnnotationView (the cleanest and the most generic way IMHO). The only difference is that the observer is now replaced with a delegate method.
Create the delegate protocol:
protocol HeadingDelegate : AnyObject {
func headingChanged(_ heading: CLLocationDirection)
}
Create MKPointAnnotation subclass that notifies the delegate. The headingDelegate property will be assigned externally from the view controller and triggered every time the heading property changes:
class Annotation : MKPointAnnotation {
weak var headingDelegate: HeadingDelegate?
var heading: CLLocationDirection {
didSet {
headingDelegate?.headingChanged(heading)
}
}
init(_ coordinate: CLLocationCoordinate2D, _ heading: CLLocationDirection) {
self.heading = heading
super.init()
self.coordinate = coordinate
}
}
Create MKAnnotationView subclass that implements the delegate:
class AnnotationView : MKAnnotationView , HeadingDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
func headingChanged(_ heading: CLLocationDirection) {
// For simplicity the affine transform is done on the view itself
UIView.animate(withDuration: 0.1, animations: { [unowned self] in
self.transform = CGAffineTransform(rotationAngle: CGFloat(heading / 180 * .pi))
})
}
}
Considering that your view controller implements both CLLocationManagerDelegate and MKMapViewDelegate there is very little left to do (not providing full view controller code here):
// Delegate method of the CLLocationManager
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
userAnnotation.heading = newHeading.trueHeading
}
// Delegate method of the MKMapView
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: NSStringFromClass(Annotation.self))
if (annotationView == nil) {
annotationView = AnnotationView(annotation: annotation, reuseIdentifier: NSStringFromClass(Annotation.self))
} else {
annotationView!.annotation = annotation
}
if let annotation = annotation as? Annotation {
annotation.headingDelegate = annotationView as? HeadingDelegate
annotationView!.image = /* arrow image */
}
return annotationView
}
The most important part is where the delegate property of the annotation (headingDelegate) is assigned with the annotation view object. This binds the annotation with it's view such that every time the heading property is modified the view's headingChanged() method is called.
NOTE: didSet{} and willSet{} property observers used here were first introduced in Swift 4.