Back to examples
advanced

Annotation models

Subclass annotation classes and implement the MGLAnnotation protocol.

      //
//  CustomAnnotationModels.swift
//

import Mapbox

// MGLAnnotation protocol reimplementation
class CustomPointAnnotation: NSObject, MGLAnnotation {

    // As a reimplementation of the MGLAnnotation protocol, we have to add mutable coordinate and (sub)title properties ourselves.
    var coordinate: CLLocationCoordinate2D
    var title: String?
    var subtitle: String?
    
    // Custom properties that we will use to customize the annotation's image.
    var image: UIImage?
    var reuseIdentifier: String?
    
    init(coordinate: CLLocationCoordinate2D, title: String?, subtitle: String?) {
        self.coordinate = coordinate
        self.title = title
        self.subtitle = subtitle
    }
}

// MGLPolyline subclass
class CustomPolyline: MGLPolyline {
    // Because this is a subclass of MGLPolyline, there is no need to redeclare its properties.
    
    // Custom property that we will use when drawing the polyline.
    var color: UIColor?
}


      
      
//
//  CustomAnnotationModels.h
//

@import Mapbox;

// MGLAnnotation protocol reimplementation
@interface CustomPointAnnotation : NSObject <MGLAnnotation>

// As a reimplementation of the MGLAnnotation protocol, we have to add mutable coordinate and (sub)title properties ourselves.
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy, nullable) NSString *title;
@property (nonatomic, copy, nullable) NSString *subtitle;

// Custom properties that we will use to customize the annotation's image.
@property (nonatomic, copy, nonnull) UIImage *image;
@property (nonatomic, copy, nonnull) NSString *reuseIdentifier;

@end

@implementation CustomPointAnnotation
@end


// MGLPolyline subclass
@interface CustomPolyline : MGLPolyline

// Because this is a subclass of MGLPolyline, there is no need to redeclare its properties.

// Custom property that we will use when drawing the polyline.
@property (nonatomic, strong, nullable) UIColor *color;

@end

@implementation CustomPolyline
@end

      
      //
//  CustomAnnotationModelViewController.swift
//

import Mapbox

class CustomAnnotationModelViewController: UIViewController, MGLMapViewDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let mapView = MGLMapView(frame: view.bounds)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.styleURL = MGLStyle.lightStyleURL(withVersion: 9)
        mapView.tintColor = .darkGray
        mapView.zoomLevel = 1
        mapView.delegate = self
        view.addSubview(mapView)
        
        // Polyline
        // Create a coordinates array with all of the coordinates for our polyline.
        var coordinates = [
            CLLocationCoordinate2D(latitude: 35,  longitude: -25),
            CLLocationCoordinate2D(latitude: 20,  longitude: -30),
            CLLocationCoordinate2D(latitude: 0,   longitude: -25),
            CLLocationCoordinate2D(latitude: -15, longitude: 0),
            CLLocationCoordinate2D(latitude: -45, longitude: 10),
            CLLocationCoordinate2D(latitude: -45, longitude: 40),
        ]
        
        let polyline = CustomPolyline(coordinates: &coordinates, count: UInt(coordinates.count))

        // Set the custom `color` property, later used in the `mapView:strokeColorForShapeAnnotation:` delegate method.
        polyline.color = .darkGray
        
        // Add the polyline to the map. Note that this method name is singular.
        mapView.addAnnotation(polyline)
        
        // Point Annotations
        // Add a custom point annotation for every coordinate (vertex) in the polyline.
        var pointAnnotations = [CustomPointAnnotation]()
        for coordinate in coordinates {
            let count = pointAnnotations.count + 1
            let point = CustomPointAnnotation(coordinate: coordinate,
                                              title: "Custom Point Annotation \(count)",
                                              subtitle: nil)
            
            // Set the custom `image` and `reuseIdentifier` properties, later used in the `mapView:imageForAnnotation:` delegate method.
            // Create a unique reuse identifier for each new annotation image.
            point.reuseIdentifier = "customAnnotation\(count)"
            // This dot image grows in size as more annotations are added to the array.
            point.image = dot(size:5 * count)
            
            // Append each annotation to the array, which will be added to the map all at once.
            pointAnnotations.append(point)
        }
        
        // Add the point annotations to the map. This time the method name is plural.
        // If you have multiple annotations to add, batching their addition to the map is more efficient.
        mapView.addAnnotations(pointAnnotations)
    }
    
    func dot(size: Int) -> UIImage {
        let floatSize = CGFloat(size)
        let rect = CGRect(x: 0, y: 0, width: floatSize, height: floatSize)
        let strokeWidth: CGFloat = 1
        
        UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale)
        
        let ovalPath = UIBezierPath(ovalIn: rect.insetBy(dx: strokeWidth, dy: strokeWidth))
        UIColor.darkGray.setFill()
        ovalPath.fill()
        
        UIColor.white.setStroke()
        ovalPath.lineWidth = strokeWidth
        ovalPath.stroke()
        
        let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        
        return image
    }
    
    // MARK: - MGLMapViewDelegate methods
    
    func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {
        if let point = annotation as? CustomPointAnnotation,
            
            let image = point.image,
            let reuseIdentifier = point.reuseIdentifier {
            
            if let annotationImage = mapView.dequeueReusableAnnotationImage(withIdentifier: reuseIdentifier) {
                // The annotatation image has already been cached, just reuse it.
                return annotationImage
            } else {
                // Create a new annotation image.
                return MGLAnnotationImage(image: image, reuseIdentifier: reuseIdentifier)
            }
        }
        
        // Fallback to the default marker image.
        return nil
    }
    
    func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor {
        if let annotation = annotation as? CustomPolyline {
            // Return orange if the polyline does not have a custom color.
            return annotation.color ?? .orange
        }
        
        // Fallback to the default tint color.
        return mapView.tintColor
    }
    
    func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        return true
    }
}


      
      
//
//  CustomAnnotationModelViewController.m
//

#import "CustomAnnotationModelViewController.h"
#import "CustomAnnotationModels.h"
@import Mapbox;

@interface CustomAnnotationModelViewController () <MGLMapViewDelegate>
@end

@implementation CustomAnnotationModelViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    MGLMapView *mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds];
    mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    mapView.styleURL = [MGLStyle lightStyleURLWithVersion:9];
    mapView.tintColor = [UIColor darkGrayColor];
    mapView.zoomLevel = 1;
    mapView.delegate = self;
    [self.view addSubview:mapView];

    // Polyline
    // Create a coordinates array with all of the coordinates for our polyline.
    CLLocationCoordinate2D coordinates[] = {
        CLLocationCoordinate2DMake(35, -25),
        CLLocationCoordinate2DMake(20, -30),
        CLLocationCoordinate2DMake( 0, -25),
        CLLocationCoordinate2DMake(-15,  0),
        CLLocationCoordinate2DMake(-45, 10),
        CLLocationCoordinate2DMake(-45, 40),
    };
    NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);

    CustomPolyline *polyline = [CustomPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates];//
    // Set the custom `color` property, later used in the `mapView:strokeColorForShapeAnnotation:` delegate method.
    polyline.color = [UIColor darkGrayColor];

    // Add the polyline to the map. Note that this method name is singular.
    [mapView addAnnotation:polyline];

    // Point Annotations
    // Add a custom point annotation for every coordinate (vertex) in the polyline.
    NSMutableArray *pointAnnotations = [NSMutableArray arrayWithCapacity:numberOfCoordinates];
    for (NSUInteger i = 0; i < numberOfCoordinates; i++) {
        NSUInteger count = pointAnnotations.count + 1;
        CustomPointAnnotation *point = [[CustomPointAnnotation alloc] init];

        point.coordinate = coordinates[i];
        point.title = [NSString stringWithFormat:@"Custom Point Annotation %lu", (unsigned long)count];

        // Set the custom `image` and `reuseIdentifier` properties, later used in the `mapView:imageForAnnotation:` delegate method.
        // Create a unique reuse identifier for each new annotation image.
        point.reuseIdentifier = [NSString stringWithFormat:@"customAnnotation%lu", (unsigned long)count];
        // This dot image grows in size as more annotations are added to the array.
        point.image = [self dotWithSize:(5 * count)];

        // Append each annotation to the array, which will be added to the map all at once.
        [pointAnnotations addObject:point];
    }

    // Add the point annotations to the map. This time the method name is plural.
    // If you have multiple annotations to add, batching their addition to the map is more efficient.
    [mapView addAnnotations:pointAnnotations];
}

- (UIImage *)dotWithSize:(NSUInteger)size {
    size = (CGFloat)size;
    CGRect rect = CGRectMake(0, 0, size, size);
    CGFloat strokeWidth = 1;

    UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]);

    UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(rect, strokeWidth, strokeWidth)];
    [UIColor.darkGrayColor setFill];
    [ovalPath fill];

    [UIColor.whiteColor setStroke];
    ovalPath.lineWidth = strokeWidth;
    [ovalPath stroke];

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
}

#pragma mark - MGLMapViewDelegate methods

- (MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id<MGLAnnotation>)annotation {
    if ([annotation isKindOfClass:[CustomPointAnnotation class]]) {
        CustomPointAnnotation *point = (CustomPointAnnotation *)annotation;
        MGLAnnotationImage *annotationImage = [mapView dequeueReusableAnnotationImageWithIdentifier:point.reuseIdentifier];

        if (annotationImage) {
            // The annotatation image has already been cached, just reuse it.
            return annotationImage;
        } else if (point.image && point.reuseIdentifier) {
            // Create a new annotation image.
            return [MGLAnnotationImage annotationImageWithImage:point.image reuseIdentifier:point.reuseIdentifier];
        }
    }

    // Fallback to the default marker image.
    return nil;
}

- (UIColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation {
    if ([annotation isKindOfClass:[CustomPolyline class]]) {
        // Return orange if the polyline does not have a custom color.
        return [(CustomPolyline *)annotation color] ?: [UIColor orangeColor];
    }

    // Fallback to the default tint color.
    return mapView.tintColor;
}

- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id<MGLAnnotation>)annotation {
    return YES;
}

@end