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 ofMGLAnnotationView
, a descendant ofUIView
.ViewController
uses these annotation views withMGLMapView
.
Like the Annotation views example, this example demonstrates how native UIView
s 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.
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()
}
}
}
#import "ViewController.h"
@import Mapbox;
// MGLAnnotationView subclass
@interface DraggableAnnotationView : MGLAnnotationView
@end
// Private interface for DraggableAnnotationView
@interface DraggableAnnotationView ()
@property (nonatomic, nullable) UIImpactFeedbackGenerator *hapticFeedback;
@end
@implementation DraggableAnnotationView
- (instancetype)initWithReuseIdentifier:(nullable NSString *)reuseIdentifier size:(CGFloat)size {
self = [self initWithReuseIdentifier:reuseIdentifier];
if (self)
{
// `draggable` is a property of MGLAnnotationView, disabled by default.
self.draggable = true;
// This property prevents the annotation from changing size when the map is tilted.
self.scalesWithViewingDistance = false;
// Begin setting up the view.
self.frame = CGRectMake(0, 0, size, size);
self.backgroundColor = [UIColor darkGrayColor];
// Use CALayer’s corner radius to turn this view into a circle.
self.layer.cornerRadius = size / 2;
self.layer.borderWidth = 1;
self.layer.borderColor = [UIColor whiteColor].CGColor;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOpacity = 0.1f;
}
return self;
}
- (void)setDragState:(MGLAnnotationViewDragState)dragState animated:(BOOL)animated {
[super setDragState:dragState animated:animated];
switch (dragState) {
case MGLAnnotationViewDragStateStarting:
printf("Starting");
[self startDragging];
break;
case MGLAnnotationViewDragStateDragging:
printf(".");
break;
case MGLAnnotationViewDragStateEnding:
case MGLAnnotationViewDragStateCanceling:
printf("Ending\n");
[self endDragging];
break;
case MGLAnnotationViewDragStateNone:
return;
}
}
// When the user interacts with an annotation, animate opacity and scale changes.
- (void)startDragging {
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:0 animations:^{
self.layer.opacity = 0.8f;
self.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.5, 1.5);
} completion:nil];
// Initialize haptic feedback generator and give the user a light thud.
if (@available(iOS 10.0, *)) {
self.hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
[self.hapticFeedback impactOccurred];
// Keep the generator prepared, as the drop feedback event will probably happen quite soon.
[self.hapticFeedback prepare];
}
}
- (void)endDragging {
self.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.5, 1.5);
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:0 animations:^{
self.layer.opacity = 1;
self.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);
} completion:nil];
// Give the user more haptic feedback when they drop the annotation, then release the current generator.
if (@available(iOS 10.0, *)) {
[self.hapticFeedback impactOccurred];
self.hapticFeedback = nil;
}
}
@end
//
// Example view controller
@interface ViewController () <MGLMapViewDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
MGLMapView *mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
mapView.styleURL = [MGLStyle lightStyleURL];
mapView.tintColor = [UIColor darkGrayColor];
mapView.zoomLevel = 1;
mapView.delegate = self;
[self.view addSubview:mapView];
// Specify coordinates for our annotations.
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0, -70),
CLLocationCoordinate2DMake(0, -35),
CLLocationCoordinate2DMake(0, 0),
CLLocationCoordinate2DMake(0, 35),
CLLocationCoordinate2DMake(0, 70),
};
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
// Fill an array with point annotations and add it to the map.
NSMutableArray *pointAnnotations = [NSMutableArray arrayWithCapacity:numberOfCoordinates];
for (NSUInteger i = 0; i < numberOfCoordinates; i++) {
CLLocationCoordinate2D coordinate = coordinates[i];
MGLPointAnnotation *point = [[MGLPointAnnotation alloc] init];
point.coordinate = coordinate;
point.title = @"To drag this annotation, first tap and hold.";
[pointAnnotations addObject:point];
}
[mapView addAnnotations:pointAnnotations];
}
#pragma 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:`.
- (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id <MGLAnnotation>)annotation {
// This example is only concerned with point annotations.
if (![annotation isKindOfClass:[MGLPointAnnotation class]]) {
return nil;
}
// For better performance, always try to reuse existing annotations. To use multiple different annotation views, change the reuse identifier for each.
DraggableAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"draggablePoint"];
// If there’s no reusable annotation view available, initialize a new one.
if (!annotationView) {
annotationView = [[DraggableAnnotationView alloc] initWithReuseIdentifier:@"draggablePoint" size:50];
}
return annotationView;
}
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id<MGLAnnotation>)annotation {
return YES;
}
@end