intermediate
Swift or Objective-C
Create a map for iOS using data-driven styling
Prerequisite
Familiarity with Xcode and either Swift or Objective-C.

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 build a map for iOS that includes a circle layer styled based on a data attribute.

finished map on an iOS device

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 also assumes that you have already begun building an iOS application that uses the Mapbox iOS SDK. If you’re new to the Mapbox iOS SDK, check out the First steps with the Mapbox iOS SDK 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

Upload data to Mapbox

In this tutorial, you will be using a vector tileset to display data in your application. You can create a vector tileset from the GeoJSON data you downloaded by uploading it to Mapbox Studio:

  1. Log into Mapbox Studio.
  2. Visit the Tilesets page.
  3. Click New tileset.
  4. Select the GeoJSON file you downloaded at the beginning of this tutorial and click Upload.
  5. A popover will appear in the bottom left showing the progress of your upload.
  6. Once the upload has “Succeeded”, the tileset will be ready to use! Click on the name of the tileset in the popover, which will open the tileset information page.
  7. Take note of the Map ID on the right side of the tileset information page. You will use the Map ID to add this tileset to your application later in this tutorial.

Initialize a map view

After the data has been uploaded to your Mapbox account, you can load it on to your map dynamically. But before you can add data, you first need to initialize a map view. Use the code below to create a simple map view that uses the Mapbox Light style and is 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 be a blank map using the Mapbox Light style.

blank light map on an iOS device

Load the source

To load the data onto the map, 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. You can find the map ID on the tileset information page for this tileset.

This source should be added when the map is finished loading, so 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

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 need to initialize and add a style layer to complete this visualization. Next, you will add a circle layer and style the circles dynamically based on an attribute in the source. In this case, you can color each individual circle based off of its AGE attribute.

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

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";

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, 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 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! You can check out the complete code for this map on our Mapbox iOS SDK examples page.

finished map on an iOS device

Next steps

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