Create a map for iOS using data-driven styling

Introduction

Data-driven styling is a powerful feature within the Mapbox iOS SDK that allows you use data attributes to style your maps. With data-driven styling, you can style map features automatically based on their individual attributes. In this tutorial, you’ll be building a map for iOS that includes a circle layer whose style is based on a data attribute.

Getting started

This guide assumes you are familiar with Objective-C or Swift. Here are the resources you’ll need before getting started:

  • An application including the Mapbox iOS SDK. This guide starts off an iOS application that has included the Mapbox iOS SDK set up with your access token. If you’re new to the Mapbox iOS SDK, you might want to check out the first steps guide to set up a simple map view first.
  • Data. We collected data from the District of Columbia’s Open Data DC that shows the location of Yoshino cherry trees planted within Washington, D.C. Each tree has a calculated AGE attribute that roughly estimates its age in years. Download GeoJSON data

Steps

Upload data to Mapbox

In this tutorial, you will create a vector tileset from our original GeoJSON data. You can upload the data through our Tilesets page in Mapbox Studio.

Initialize a map view

Once the data has been uploaded to your Mapbox account, you can load it on to your map dynamically. Here, we start with a simple map view that uses the Mapbox Light style, centered on Washington, D.C.

import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
    
    var mapView: MGLMapView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Create a new map view using the Mapbox Light style.
        let lightStyleURL = MGLStyle.lightStyleURL(withVersion: 9)
        mapView = MGLMapView(frame: view.bounds, styleURL: lightStyleURL)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.tintColor = .darkGray
        
        // Set the map’s center coordinate and zoom level.
        mapView.setCenter(CLLocationCoordinate2D(latitude: 38.897, longitude: -77.039), zoomLevel: 10.5, animated: false)

        mapView.delegate = self
        view.addSubview(mapView)
    }
}
#import "ViewController.h"
@import Mapbox;

@interface ViewController () <MGLMapViewDelegate>

@property (nonatomic) MGLMapView *mapView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Create a new map view using the Mapbox Light style.
    NSURL *lightStyleURL = [MGLStyle lightStyleURLWithVersion:9];
    self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:lightStyleURL];
    
    self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.mapView.tintColor = [UIColor darkGrayColor];
    
    // Set the map’s center coordinate and zoom level.
    self.mapView.centerCoordinate = CLLocationCoordinate2DMake(38.897, -77.039);
    self.mapView.zoomLevel = 10.5;
    
    self.mapView.delegate = self;
    [self.view addSubview:self.mapView];
}

@end

The result will look like this:

Load the source

To load the data onto the map, you will add the vector tileset to the map dynamically as an MGLVectorSource. To do this, you’ll need to use the map ID of the vector tileset created earlier in order to reference it in the configurationURL of your MGLVectorSource.

Since we would only want to add the this source when the map is finished loading, you’ll need to create your source within the -mapView:didFinishLoadingStyle: delegate method.

// Wait until the style is loaded before modifying the map style.
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
    
    // "mapbox://examples.2uf7qges" is the map ID referencing a tileset
    // created from the GeoJSON data uploaded earlier.
    let source = MGLVectorSource(identifier: "trees", configurationURL: URL(string: "mapbox://examples.2uf7qges")!)

    style.addSource(source)
// Wait until the style is loaded before modifying the map style.
- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
    
    // "mapbox://examples.2uf7qges" is the map ID referencing a tileset
    // created from the GeoJSON data uploaded earlier.
    MGLSource *source = [[MGLVectorSource alloc] initWithIdentifier:@"trees" configurationURL:[NSURL URLWithString:@"mapbox://examples.2uf7qges"]];
    
    [self.mapView.style addSource:source];
}

Style the source based on an attribute

You can style these circles dynamically based on a data attribute that the source data has. In this case, you can color each individual circle based off of its AGE attribute.

At this point, the source has been added to the map, but it has not been styled yet - so it won’t appear if you try to run the application. You will need to initialize and add a style layer to complete this visualization.

Within the same -mapView:didFinishLoadingStyle: delegate method, first initialize an MGLCircleStyleLayer below the source created above:

let layer = MGLCircleStyleLayer(identifier: "tree-style", source: source)

// The source name from the source's TileJSON metadata: mapbox.com/api-documentation/#retrieve-tilejson-metadata
layer.sourceLayerIdentifier = "yoshino-trees-a0puw5"
MGLCircleStyleLayer *layer = [[MGLCircleStyleLayer alloc] initWithIdentifier: @"tree-style" source:source];

// The source name from the source's TileJSON metadata: mapbox.com/api-documentation/#retrieve-tilejson-metadata
layer.sourceLayerIdentifier = @"yoshino-trees-a0puw5";

Note: The sourceLayerIdentifier refers to the source name from your tileset’s metadata, also known as TileJSON. You can retrieve the source name of your tileset by using the Maps API as shown here.

Each tree will have a different color based on what age range it falls within. Below is the breakdown of which colors will apply to each age range:

Age in years UIColor Color
0 UIColor(red:1.00, green:0.72, blue:0.85, alpha:1.0)
2 UIColor(red:0.69, green:0.48, blue:0.73, alpha:1.0)
4 UIColor(red:0.61, green:0.31, blue:0.47, alpha:1.0)
7 UIColor(red:0.43, green:0.20, blue:0.38, alpha:1.0)
16 UIColor(red:0.33, green:0.17, blue:0.25, alpha:1.0)

Next, you will create a dictionary of these values that represent the above table:

let stops = [
  0: MGLStyleValue(rawValue: UIColor(red:1.00, green:0.72, blue:0.85, alpha:1.0)),
  2: MGLStyleValue(rawValue: UIColor(red:0.69, green:0.48, blue:0.73, alpha:1.0)),
  4: MGLStyleValue(rawValue: UIColor(red:0.61, green:0.31, blue:0.47, alpha:1.0)),
  7: MGLStyleValue(rawValue: UIColor(red:0.43, green:0.20, blue:0.38, alpha:1.0)),
  16: MGLStyleValue(rawValue: UIColor(red:0.33, green:0.17, blue:0.25, alpha:1.0))
]
NSDictionary *stops = @{
    @0: [MGLStyleValue valueWithRawValue:[UIColor colorWithRed:1.00 green:0.72 blue:0.85 alpha:1.0]],
    @2: [MGLStyleValue valueWithRawValue:[UIColor colorWithRed:0.69 green:0.48 blue:0.73 alpha:1.0]],
    @4: [MGLStyleValue valueWithRawValue:[UIColor colorWithRed:0.61 green:0.31 blue:0.47 alpha:1.0]],
    @7: [MGLStyleValue valueWithRawValue:[UIColor colorWithRed:0.43 green:0.20 blue:0.38 alpha:1.0]],
    @16: [MGLStyleValue valueWithRawValue:[UIColor colorWithRed:0.33 green:0.17 blue:0.25 alpha:1.0]]
};

Now that you have defined the stops, you can specify each circle’s color in the MGLCircleStyleLayer based off of its age, and then add this style layer to the map:

// Style the circle layer color based on the above categorical stops
layer.circleColor = MGLStyleValue<UIColor>(interpolationMode: .interval,
    sourceStops: stops,
    attributeName: "AGE",
    options: nil)
        
layer.circleRadius = MGLStyleValue(rawValue: 3)
        
style.addLayer(layer)
// Style the circle layer color based on the above categorical stops.
layer.circleColor = [MGLStyleValue valueWithInterpolationMode: MGLInterpolationModeInterval
    sourceStops: stops
    attributeName: @"AGE"
    options: nil];
    
layer.circleRadius = [MGLStyleValue valueWithRawValue:@3];
    
[self.mapView.style addLayer:layer];

In the above code, circleColor is set to an MGLStyleValue that is an MGLSourceStyleFunction, using the stops defined earlier. The radius of each circle is also defined. To complete this visualization, you will need to call the addLayer() method on your style layer to add this style layer to your map.

Finished product

Check out your finished map - great job! You can check out the full example here.

The Mapbox iOS SDK provides a variety of ways beautiful data visualizations dynamically. Keep reading our API documentation to learn more!

Next article:

Set the initial position of your map

Additional questions? Ask our support team or learn more about How Mapbox Works.