iOS 10 heading arrow for MKUserLocation dot

前端 未结 4 2029
谎友^
谎友^ 2020-12-14 02:24

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

4条回答
  •  情书的邮戳
    2020-12-14 03:06

    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.

    1. Create the delegate protocol:

      protocol HeadingDelegate : AnyObject {
          func headingChanged(_ heading: CLLocationDirection)
      }
      
    2. 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
          }
      }
      
    3. 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))
              })
          }
      }
      
    4. 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.

提交回复
热议问题