Skip to main content

Draggable annotation views

A newer version of the Maps SDK is available

This page uses v6.4.1 of the Mapbox Maps SDK. A newer version of the SDK is available. Learn about the latest version, v11.9.0, in the Maps SDK documentation.

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.

Like 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, see the Markers and annotations guide.

ViewController
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:
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()
}
}
}