Closed samcrawford closed 2 years ago
I've found a solution. I can now drag point annotations, and I haven't needed to modify the core MapboxMaps project to do so. For the benefit of others, I've pasted an excerpt of my code below. This was achieved using Mapbox Maps for iOS 10.0.0.
My code is inspired by https://github.com/mapbox/mapbox-maps-ios/issues/501#issuecomment-884744996
Note to Mapbox people: Feel free to use this example if you wish. I hope you can improve the examples in your documentation (especially for things like this that existed in the previous SDK version), this was quite painful to figure out.
class MapViewController: UIViewController, AnnotationInteractionDelegate, UIGestureRecognizerDelegate {
private var mapView: MapView!
private var pointAnnotationManager: PointAnnotationManager!
private var draggedAnnotation: PointAnnotation?
private var panGesture: UIPanGestureRecognizer!
override func viewDidLoad() {
...
let mapView = MapView(...)
mapView.mapboxMap.onNext(.mapLoaded) { (event) in
self.mapViewDidLoad(style: self.mapView.mapboxMap.style)
}
}
func mapViewDidLoad(style: Style) {
...
// Setup annotations using the Mapbox provided PointAnnotationManager.
// Note that the delegate only handles clicks, not drags.
self.pointAnnotationManager = mapView.annotations.makePointAnnotationManager(id: "annotations")
self.pointAnnotationManager.delegate = self
// Annotation drag setup
self.panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleMapPan(sender:)))
panGesture.minimumNumberOfTouches = 1
panGesture.maximumNumberOfTouches = 1
panGesture.delegate = self
// Very important: Make sure that other UIPanGestureRecognizers on the mapview only fire
// when our annotation recognizer has failed. Note we mark it as failed inside handleMapPan
// if we aren't panning on an annotation.
for recognizer in mapView.gestureRecognizers! where recognizer is UIPanGestureRecognizer {
recognizer.require(toFail: panGesture)
}
mapView.addGestureRecognizer(panGesture)
}
// This is need to let the separate pan gesture recognisers work on both the annotations and
// the panning over the map.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer == self.panGesture &&
otherGestureRecognizer == mapView.gestures.panGestureRecognizer
}
// Used for handling panning to detect annotation dragging
@IBAction @objc func handleMapPan(sender: UIPanGestureRecognizer) {
switch sender.state {
case .began:
// Look for features in the annotation layer under the pan movement
let options = RenderedQueryOptions(layerIds: [ self.pointAnnotationManager.id ], filter: nil)
mapView.mapboxMap.queryRenderedFeatures(
at: sender.location(in: sender.view),
options: options,
completion: { result in
switch result {
case .success(let queriedFeatures):
// Get the identifiers of all the queried features
let queriedFeatureIds: [String] = queriedFeatures.compactMap {
guard case let .string(featureId) = $0.feature.identifier else {
return nil
}
return featureId
}
// Find if any `queriedFeatureIds` match an annotation's `id`
let pannedAnnotations = self.pointAnnotationManager.annotations.filter { queriedFeatureIds.contains($0.id) }
// If we determined the user has dragged an annotation, store it in a global var and we'll update its
// position in the .changed event below.
if !pannedAnnotations.isEmpty {
self.draggedAnnotation = pannedAnnotations.first!
} else {
// Very important:
// If the user did not pan over any annotations, we need to mark this
// gesture as failed, so we can fall through to map panning instead.
sender.state = .failed
}
case .failure:
return
}
}
)
case .changed:
guard let annotation = draggedAnnotation else {
return
}
let targetPoint = self.mapView.mapboxMap.coordinate(for: sender.location(in: sender.view))
// For some reason Mapbox doesn't let us update the geometry of an existing annotation
// so we have to create a whole new one.
var newAnnotation = PointAnnotation(id: annotation.id, coordinate: targetPoint)
newAnnotation.image = annotation.image
var newAnnotations = self.pointAnnotationManager.annotations.filter { an in
return an.id != annotation.id
}
newAnnotations.append(newAnnotation)
self.pointAnnotationManager.annotations = newAnnotations
case .ended:
if self.draggedAnnotation != nil, let id = Int(self.draggedAnnotation!.id) {
let targetPoint = self.mapView.mapboxMap.coordinate(for: sender.location(in: sender.view))
// Optionally notify some other delegate to tell them the drag finished.
delegate?.mapViewControllerDidDragAnnotation(id, coordinate: targetPoint)
// Reset our global var containing the annotation currently being dragged
self.draggedAnnotation = nil
}
default:
return
}
}
// This is standard PointAnnotationManager stuff, unrelated to dragging.
func annotationManager(_ manager: AnnotationManager, didDetectTappedAnnotations annotations: [Annotation]) {
...
}
After few hours of struggles, I find out, that it is necessary to change this line of code when using version 10.0.1
let options = RenderedQueryOptions(layerIds: [ self.pointAnnotationManager.id ], filter: nil)
to this one
let options = RenderedQueryOptions(layerIds: [ self.pointAnnotationManager.layerId ], filter: nil)
@samcrawford, I would actually change UIPanGestureRecognizer
to UILongPressGestureRecognizer
.
With UILongPressGestureRecognizer
, you get the same features as UIPanGestureRecognizer
but you can notify the user that they have grabbed the annotation. (Make the annotation bigger or something).
MapView does not have UILongPressGestureRecognizer
, which means that you don't interfere with MapView gestures.
Closing in favor of https://github.com/mapbox/mapbox-maps-ios/issues/501
Pre-v10, Mapbox provided support for draggable annotations, and documented a nice example at https://web.archive.org/web/20210124190424/https://docs.mapbox.com/ios/maps/examples/draggable-views/ (using an archive.org link because the mapbox.com link no longer exists).
In the v10 iOS SDK, the migration guide says:
Can Mapbox provide an example, or at least some more detailed hints as to how we can get back draggable annotations?
I'd be happy to contribute a worked example if I can get some hints as to what the "advanced usage of various style APIs" might be!