Custom dynamic markers with the Mapbox iOS SDK and PaintCode

Tom MacWright
Fri, 26 Aug 2016 12:00:00 -0400

Using PaintCode and the Mapbox iOS SDK, we can create a workflow for custom markers and interface elements in an app without paying a price in performance or busywork.

Building visually rich, geographically-centered applications leads naturally to a focus on small details. Why go through the effort of designing custom UI elements and a strong brand, but then use default map markers and styles?

Recently I’ve been working with Maya on a mobile application with the newly-launched Mapbox Studio dataset editor. Designing this kind of application often starts with the UI being statically designed in an application like Sketch, Inkscape, or Illustrator. In this case, Maya used Sketch to define the look of the application.

Sketch natively supports the SVG format for vector graphics. SVG has quickly replaced raster PNG files for web app design elements due to its efficiency with flat graphics and effortless support of scaling for high-DPI displays. Unfortunately, it isn’t as easy to use an SVG file in an iOS application as it is with the web, and you can’t control aspects of SVG graphics the same ways that you can with CSS.

Luckily, PaintCode bridges this gap. From Sketch, we export individual elements as SVG files, then import those into PaintCode. In PaintCode, we then choose colors or other attributes and make them controllable with parameters.

PaintCode then allows you to export these graphics as StyleKits: generated Objective-C or Swift code that, instead of describing shapes using SVG’s declarative paths, draws graphics dynamically using UIBezierPath and other native functionality. In this case, I’ve exported the graphics as GeoSheetStyleKit, and this marker is exposed as the method .imageOfMarker(_:), which receives a fill color and returns a UIImage instance.

func mapView(mapView: MGLMapView, imageForAnnotation annotation: MGLAnnotation) -> MGLAnnotationImage? {
    guard let annotation = annotation as? CustomMarker else {
        return nil
    }

    let blueColor = UIColor(red:0.0706, green:0.6039, blue:0.9294, alpha:1.0)
    let yellowColor = UIColor(red:1.0000, green:0.8235, blue:0.0275, alpha:1.0)
    let greenColor = UIColor(red:0.4000, green:0.7255, blue:0.4118, alpha:1.0)
    let redColor = UIColor(red:1.0000, green:0.4157, blue:0.0000, alpha:1.0)

    switch annotation.feature!.properties.state ?? "" {
    case "unsafe":
        color = redColor
        reuseid = "marker-red"
    case "damaged":
        color = yellowColor
        reuseid = "marker-yellow"
    case "inspected":
        color = greenColor
        reuseid = "marker-green"
    default:
        color = blueColor
        reuseid = "marker-blue"
    }

    var annotationImage = mapView.dequeueReusableAnnotationImageWithIdentifier(reuseid)

    // if this image was cached, then we can reuse it instead of drawing
    // it from scratch
    if annotationImage == nil {
        // here's where we use the StyleKit to draw a new image based
        // on our picked color
        var image = GeoSheetStyleKit.imageOfMarker(markerColor: color)
        image = image.imageWithAlignmentRectInsets(UIEdgeInsets(top: 0, left: 0, bottom: image.size.height/2, right: 0))
        annotationImage = MGLAnnotationImage(image: image, reuseIdentifier: reuseid)
    }

    return annotationImage
}

This uses the custom marker image example as a starting point, and shows off some of the smart default functionality of markers on iOS - for instance, we save marker instances with a reuseIdentifier so that they’re cached rather than constantly redrawn.

Note that this uses one marker resource, and dynamically draws it with different colors. It’s more flexible than tinting a raster image, which would tint the entire image including the gray shadow in this one. Using this method, we can color individual parts of the image and control other style properties as well. This also handles different pixel densities without requiring multiple versions of an image file.

If you’re feeling brave, you can also write drawing code from scratch to do something similar with iOS’s native Core Graphics functionality. The Mapbox iOS SDK’s API is flexible enough to support many new and fun possibilities. Enjoy!