Draggable annotation views

This example uses two classes within a single file:

  • DraggableAnnotationView is a subclass of MGLAnnotationView, a descendant of UIView.
  • ViewController uses these annotation views with MGLMapView.

Similar to the Annotation views example, this example demonstrates how native UIViews can be used to mark locations on a map. For more information about working with gesture recognizers, see our User interactions guide.

To learn about more ways to add points to a map, check out the Markers and annotations guide.

import Mapbox
// Example view controller
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.styleURL = MGLStyle.lightStyleURL
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 {
return DraggableAnnotationView(reuseIdentifier: "draggablePoint", size: 50)
}
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
}
// MGLAnnotationView subclass
class DraggableAnnotationView: MGLAnnotationView {
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
}
// 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:
print(".", terminator: "")
case .ending, .canceling:
print("Ending")
endDragging()
case .none:
return
}
}
// 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()
}
}
}