iOS 10 heading arrow for MKUserLocation dot

Ben Williams picture Ben Williams · Sep 29, 2016 · Viewed 8k times · Source

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 MKMapView in my own apps?

enter image description here

Edit: I'd be happy to do this manually, but I'm not sure if it's possible? Can I add an annotation to the map and have it follow the user's location, including animated moves?

Answer

David T picture David T · Nov 25, 2016

I also experienced this same issue (needing an orientation indicator without having the map spin around, similar to the Apple Maps app). Unfortunately Apple has not yet made the 'blue icon for heading' API available.

I created the following solution derived from @alku83's implementation.

  1. Ensure the class conforms to MKViewDelegate
  2. Add the delegate method to add a blue arrow icon to the maps location dot

    func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
        if views.last?.annotation is MKUserLocation {
            addHeadingView(toAnnotationView: views.last!)
        }
    }
    
  3. Add the method to create the 'blue arrow icon'.

    func addHeadingView(toAnnotationView annotationView: MKAnnotationView) {
        if headingImageView == nil {
            let image = #YOUR BLUE ARROW ICON#
            headingImageView = UIImageView(image: image)
            headingImageView!.frame = CGRect(x: (annotationView.frame.size.width - image.size.width)/2, y: (annotationView.frame.size.height - image.size.height)/2, width: image.size.width, height: image.size.height)
            annotationView.insertSubview(headingImageView!, at: 0)
            headingImageView!.isHidden = true
         }
    }
    
  4. Add var headingImageView: UIImageView? to your class. This is mainly needed to transform/rotate the blue arrow image.

  5. (In a different class/object depending on your architecture) Create a location manager instance, with the class conforming to CLLocationManagerDelegate protocol

    lazy var locationManager: CLLocationManager = {
        let manager = CLLocationManager()
        // Set up your manager properties here
        manager.delegate = self
        return manager
    }()
    
  6. Ensure your location manager is tracking user heading data locationManager.startUpdatingHeading() and that it stops tracking when appropriate locationManager.stopUpdatingHeading()

  7. Add var userHeading: CLLocationDirection? which will hold the orientation value

  8. Add the delegate method to be notified of when the heading values change, and change the userHeading value appropriately

    func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
         if newHeading.headingAccuracy < 0 { return }
    
         let heading = newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading
         userHeading = heading
         NotificationCenter.default.post(name: Notification.Name(rawValue: #YOUR KEY#), object: self, userInfo: nil)
        }
    
  9. Now in your class conforming to MKMapViewDelegate, add the method to 'transform' the orientation of the heading image

       func updateHeadingRotation() {
            if let heading = # YOUR locationManager instance#,
                let headingImageView = headingImageView {
    
                headingImageView.isHidden = false
                let rotation = CGFloat(heading/180 * Double.pi)
                headingImageView.transform = CGAffineTransform(rotationAngle: rotation)
            }
        }