Adonis Dumadapat
Adonis Dumadapat

Reputation: 51

iOS Mapbox Updating the map while dragging annotation reverts the annotation to original coordinates

I'm trying to update some map components while dragging an annotations like highlighting a specific MGLPolygon and panning the map if the annotation is already dragged near the edge. I will use the later for this problem.

I tried the code https://docs.mapbox.com/ios/maps/examples/draggable-views/ and added some lines. Here's the exact copy with my changes.

import Mapbox

// Example view controller
class ViewController: UIViewController, MGLMapViewDelegate {

  var mapView: MGLMapView!

  override func viewDidLoad() {
    super.viewDidLoad()

    mapView = MGLMapView(frame: view.bounds)
    mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    mapView.styleURL = MGLStyle.streetsStyleURL
    mapView.tintColor = .darkGray
    mapView.zoomLevel = 1
    mapView.delegate = self
    view.addSubview(mapView)

    // Specify coordinates for our annotations.
    let coordinates = [
      CLLocationCoordinate2D(latitude: 0, longitude: -70),
      CLLocationCoordinate2D(latitude: 0, longitude: -35),
      CLLocationCoordinate2D(latitude: 0, longitude: 0),
      CLLocationCoordinate2D(latitude: 0, longitude: 35),
      CLLocationCoordinate2D(latitude: 0, longitude: 70)
    ]

    // Fill an array with point annotations and add it to the map.
    var pointAnnotations = [MGLPointAnnotation]()
    for coordinate in coordinates {
      let point = MGLPointAnnotation()
      point.coordinate = coordinate
      point.title = "To drag this annotation, first tap and hold."
      pointAnnotations.append(point)
    }

    mapView.addAnnotations(pointAnnotations)
  }

  // MARK: - MGLMapViewDelegate methods

  // This delegate method is where you tell the map to load a view for a specific annotation. To load a static MGLAnnotationImage, you would use `-mapView:imageForAnnotation:`.
  func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
    // This example is only concerned with point annotations.
    guard annotation is MGLPointAnnotation else {
      return nil
    }

    // For better performance, always try to reuse existing annotations. To use multiple different annotation views, change the reuse identifier for each.
    if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "draggablePoint") {
      return annotationView
    } else {
      let dav = DraggableAnnotationView(reuseIdentifier: "draggablePoint", size: 50)
      dav.mapView = mapView
      return dav
    }
  }

  func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
    return true
  }
}

// MGLAnnotationView subclass
class DraggableAnnotationView: MGLAnnotationView {
  var mapView: MGLMapView!
  var screen: CGRect!
  var mapBounds: CGRect!

  init(reuseIdentifier: String, size: CGFloat) {
    super.init(reuseIdentifier: reuseIdentifier)

    // `isDraggable` is a property of MGLAnnotationView, disabled by default.
    isDraggable = true

    // This property prevents the annotation from changing size when the map is tilted.
    scalesWithViewingDistance = false

    // Begin setting up the view.
    frame = CGRect(x: 0, y: 0, width: size, height: size)

    backgroundColor = .darkGray

    // Use CALayer’s corner radius to turn this view into a circle.
    layer.cornerRadius = size / 2
    layer.borderWidth = 1
    layer.borderColor = UIColor.white.cgColor
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOpacity = 0.1

    screen = UIScreen.main.bounds
    mapBounds = CGRect(
      x: screen.origin.x + 20,
      y: screen.origin.y + 20,
      width: screen.size.width - 40,
      height: screen.size.height - 40)
  }

  // These two initializers are forced upon us by Swift.
  override init(frame: CGRect) {
    super.init(frame: frame)
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  // Custom handler for changes in the annotation’s drag state.
  override func setDragState(_ dragState: MGLAnnotationViewDragState, animated: Bool) {
    super.setDragState(dragState, animated: animated)

    switch dragState {
    case .starting:
      print("Starting", terminator: "")
      startDragging()
    case .dragging:
      let pointCoordinate = self.mapView.convert(center, toCoordinateFrom: nil)

      if mapBounds.contains(center) {
        DispatchQueue.main.async {
          self.mapView.setCenter(pointCoordinate, animated: true)
        }
      }

      print(".", terminator: "")
    case .ending, .canceling:
      print("Ending")
      endDragging()
    case .none:
      break
    @unknown default:
      fatalError("Unknown drag state")
    }
  }

  // When the user interacts with an annotation, animate opacity and scale changes.
  func startDragging() {
    UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
      self.layer.opacity = 0.8
      self.transform = CGAffineTransform.identity.scaledBy(x: 1.5, y: 1.5)
    }, completion: nil)

    // Initialize haptic feedback generator and give the user a light thud.
    if #available(iOS 10.0, *) {
      let hapticFeedback = UIImpactFeedbackGenerator(style: .light)
      hapticFeedback.impactOccurred()
    }
  }

  func endDragging() {
    transform = CGAffineTransform.identity.scaledBy(x: 1.5, y: 1.5)
    UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
      self.layer.opacity = 1
      self.transform = CGAffineTransform.identity.scaledBy(x: 1, y: 1)
    }, completion: nil)

    // Give the user more haptic feedback when they drop the annotation.
    if #available(iOS 10.0, *) {
      let hapticFeedback = UIImpactFeedbackGenerator(style: .light)
      hapticFeedback.impactOccurred()
    }
  }
}

Everytime the self.mapView.setCenter(pointCoordinate, animated: true) gets called, the annotations goes back and forth to its original position.

Upvotes: 4

Views: 1308

Answers (1)

Sarthak Mishra
Sarthak Mishra

Reputation: 1114

Here is the code explaining the Adonis's solution. Essentially add a pan gesture to a custom annotation's view and update the coords as and when the annotation is panned.

class CustomDraggableAnnotaionView: MGLAnnotationView {


required init(
    reuseIdentifier: String?,
    image: UIImage?,
    annotation: CustomMapGLAnnotaion
) {
    super.init(reuseIdentifier: reuseIdentifier)

    setupDraggableAnnotations()
    self.layer.zPosition = 10
}

required init?(coder _: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

// MARK: - Draggable annotation handlers

private func setupDraggableAnnotations() {
    addDraggableAnnotationGestureRecognizers()
}

private func addDraggableAnnotationGestureRecognizers() {
    let panGesture = UIPanGestureRecognizer(
        target: self,
        action: #selector(self.draggedView(_:))
    )
    let tapGesture = UITapGestureRecognizer(
        target: self,
        action: #selector(self.tappedAnnotation(_:))
    )
    self.isUserInteractionEnabled = true
    self.addGestureRecognizer(panGesture)
    self.addGestureRecognizer(tapGesture)
    for recognizer in self.gestureRecognizers! where recognizer is UITapGestureRecognizer {
        tapGesture.require(toFail: recognizer)
    }
    for recognizer in self.gestureRecognizers! where recognizer is UIPanGestureRecognizer {
        panGesture.require(toFail: recognizer)
    }
}

@objc func draggedView(_ sender: UIPanGestureRecognizer) {
    annotationObject?.draggable!.isCurrentlyDragging = true
    let point = sender.location(in: MapManager.shared.mapView)
    let coordinates = MapManager.shared.mapView.convert(
        point,
        toCoordinateFrom: MapManager.shared.mapView
    )

    annotationObject?.coordinate = coordinates

    if sender.state == .ended {
        // endDragging()
    } else if sender.state == .began {
        // startDragging()
        annotationObject?.draggable!.handler.didStartDragging()
    } else {
        // 
    }
}

}

Upvotes: 1

Related Questions