Migrating from Style Functions to Expressions

Runtime Styling enables you to modify every aspect of the map’s appearance dynamically as a user interacts with your application. Developers can specify in advance how a layout or paint attribute will vary as the zoom level changes or how the appearance of individual features vary based on metadata provided by a content source.

With Mapbox Maps SDK for iOS v4.0.0, style functions have been replaced with expressions. These provide even more tools for developers who want to style their maps dynamically. This guide outlines some tips for migrating from style functions to expressions, and offers an overview of some things that developers can do with expressions.

An expression is represented at runtime by the NSExpression class. Expressions can be used to style paint and layout properties based on zoom level, data attributes, or a combination of the two.

A constant expression can also be assigned to a style property. For example, the opacity of a fill style layer can be set to a constant value between 0 and 1.

The documentation for each individual style layer property notes which non-constant expressions are enabled for that property. Style functions supported four interpolation modes: exponential, interval, categorical, and identity.

This guide uses earthquake data from the U.S. Geological Survey. Under each interpolation mode, the style function implementation will be shown, followed by the current syntax.

For more information about how to work with GeoJSON data in our iOS SDK, please see our working with GeoJSON data guide. To learn more about supported expressions, see our “Predicates and Expressions” guide. The “Predicates and Expressions” guide also outlines Mapbox custom functions that can be used to dynamically style a map.

Stops

Stops are dictionary keys that are associated with layer attribute values. Constant values no longer need to be wrapped as style values when they are values in a stops dictionary.

Style function syntax:

let stops = [
    0: MGLStyleValue<UIColor>(rawValue: .yellow),
    2.5: MGLStyleValue(rawValue: .orange),
    5: MGLStyleValue(rawValue: .red),
    7.5: MGLStyleValue(rawValue: .blue),
    10: MGLStyleValue(rawValue: .white),
]

Current syntax:

// Swift sample on how to populate a stepping expression with multiple stops.
// Create a color ramp.
let stops: [NSNumber: UIColor] = [
    0: .yellow,
    2.5: .orange,
    5: .red,
    7.5: .blue,
    10: .white,
]

// Based on the zoom and `stops`, change the color.
var functionExpression = NSExpression(forMGLStepping: .zoomLevelVariable,
                                      from: NSExpression(forConstantValue: stops[0]),
                                      stops: NSExpression(forConstantValue: stops))

// Based on zoom and `stopsLineWidth`, set the Line width.
let initialValue = 4.0
let stopsLineWidth = [
    11.0: initialValue,
    14.0: 6.0,
    20.0: 18.0]

functionExpression = NSExpression(
    forMGLStepping: .zoomLevelVariable,
    from: NSExpression(forConstantValue: initialValue),
    stops: NSExpression(forConstantValue: stopsLineWidth)
)

Interpolation mode

Style functions supported four interpolation modes: exponential/linear, interval, categorical, and identity. For more information about supported custom expressions, please see the “Predicates and Expressions” guide.

Linear

+[NSExpression(MGLAdditions) mgl_expressionForInterpolatingExpression:withCurveType:parameters:stops:] takes the interpolation type as a parameter. If you previously used the default interpolation base, use the curve type MGLExpressionInterpolationMode.linear. See the mgl_interpolate:withCurveType:parameters:stops: documentation for more details.

The stops dictionary below, shows colors that continuously shift from yellow to orange to red to blue to white based on the attribute value.

Style function syntax:

let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")!
let symbolSource = MGLSource(identifier: "source")
let symbolLayer = MGLSymbolStyleLayer(identifier: "place-city-sm", source: symbolSource)

let source = MGLShapeSource(identifier: "earthquakes", url: url, options: nil)
mapView.style?.addSource(source)

let stops = [
    0: MGLStyleValue<UIColor>(rawValue: .yellow),
    2.5: MGLStyleValue(rawValue: .orange),
    5: MGLStyleValue(rawValue: .red),
    7.5: MGLStyleValue(rawValue: .blue),
    10: MGLStyleValue(rawValue: .white),
]

let layer = MGLCircleStyleLayer(identifier: "circles", source: source)
layer.circleColor = MGLStyleValue(interpolationMode: .exponential,
                                  sourceStops: stops,
                                  attributeName: "mag",
                                  options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .green)])
layer.circleRadius = MGLStyleValue(rawValue: 10)
mapView.style?.insertLayer(layer, below: symbolLayer)

Current syntax:

let url = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")!
let symbolSource = MGLSource(identifier: "source")
let symbolLayer = MGLSymbolStyleLayer(identifier: "place-city-sm", source: symbolSource)

let source = MGLShapeSource(identifier: "earthquakes", url: url, options: nil)
let mag = 1.0  // Update based on earthquake GeoJSON data
mapView.style?.addSource(source)

let stops: [NSNumber: UIColor] = [
    0: .yellow,
    2.5: .orange,
    5: .red,
    7.5: .blue,
    10: .white,
]

let layer = MGLCircleStyleLayer(identifier: "circles", source: source)

let circleExpression : NSExpression
if #available(iOS 15, *) {
    circleExpression = NSExpression(
        forMGLInterpolating: NSExpression(forConstantValue: mag),
        curveType: .linear,
        parameters: nil,
        stops: NSExpression(forConstantValue: stops))
} else {
    // This works up to iOS 14.5
    circleExpression = NSExpression(
        format: "mgl_interpolate:withCurveType:parameters:stops:(mag, 'linear', nil, %@)",
        stops)
}

layer.circleColor = circleExpression
layer.circleRadius = NSExpression(forConstantValue: 10)
mapView.style?.insertLayer(layer, below: symbolLayer)

Exponential

If you previously used an interpolation base greater than 0 (other than 1), you can use MGLExpressionInterpolationMode.exponential as the curve type for +[NSExpression(MGLAdditions) mgl_expressionForInterpolatingExpression:withCurveType:parameters:stops:] or 'exponential' as the curve type for mgl_interpolate:withCurveType:parameters:stops:. The parameters argument takes that interpolation base. This interpolates between values exponentially, creating an accelerated ramp effect.

Here’s a visualization from Mapbox Studio (see Mapbox Studio and iOS) comparing interpolation base values of 1.5 and 0.5 based on zoom. In order to convert camera style functions, use $zoomLevel or MGL_FUNCTION('zoomLevel') as the attribute key.

The example below increases a layer’s circleRadius exponentially based on a map’s zoom level. The interpolation base is 1.5.

Style function syntax:

let stops = [
    12: MGLStyleValue<NSNumber>(rawValue: 0.5),
    14: MGLStyleValue(rawValue: 2),
    18: MGLStyleValue(rawValue: 18),
]

layer.circleRadius = MGLStyleValue(interpolationMode: .exponential,
                                   cameraStops: stops,
                                   options: [.interpolationBase: 1.5])

Current syntax:

let stops = [
    12: 0.5,
    14: 2,
    18: 18,
]

layer.circleRadius = NSExpression(forMGLInterpolating: .zoomLevelVariable,
                                  curveType: .exponential,
                                  parameters: NSExpression(forConstantValue: 1.5),
                                  stops: NSExpression(forConstantValue: stops))

Interval

Steps, or intervals, create a range using the keys from the stops dictionary. The range is from the given key to just less than the next key. The attribute values that fall into that range are then styled using the layout or paint value assigned to that key. You can use the +[NSExpression(MGLAdditions) mgl_expressionForSteppingExpression:fromExpression:stops:] method or the custom function mgl_step:from:stops: for cases where you previously used interval interpolation mode. The first parameter takes the feature attribute name and the second parameter (from:) optionally takes the default or fallback value for that function. The final parameter takes a stops dictionary as an argument.

When we use the stops dictionary given above with an 'mgl_step:from:stops:', we create ranges where earthquakes with a magnitude of 0 to just less than 2.5 would be yellow, 2.5 to just less than 5 would be orange, and so on.

Style function syntax:

let stops = [
    0: MGLStyleValue<UIColor>(rawValue: .yellow),
    2.5: MGLStyleValue(rawValue: .orange),
    5: MGLStyleValue(rawValue: .red),
    7.5: MGLStyleValue(rawValue: .blue),
    10: MGLStyleValue(rawValue: .white),
]

layer.circleColor = MGLStyleValue(interpolationMode: .interval,
                                  sourceStops: stops,
                                  attributeName: "mag",
                                  options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .green)])

Current syntax:

let stops: [NSNumber: UIColor] = [
    0: .yellow,
    2.5: .orange,
    5: .red,
    7.5: .blue,
    10: .white,
]

layer.circleColor = NSExpression(forMGLStepping: .zoomLevelVariable,
                                 from: NSExpression(forConstantValue: UIColor.green),
                                 stops: NSExpression(forConstantValue: stops))

Categorical

Categorical interpolation mode took a stops dictionary. If the value for a specified feature attribute name matched one in that stops dictionary, the style value for that attribute value would be used. Categorical style functions can now be replaced with MGL_MATCH.

MGL_MATCH takes an initial condition, which in this case is an attribute key. This is followed by possible matches for that key and the value to assign to the layer property if there is a match. The final argument can be a default style value that is to be used if none of the specified values match.

There are three main types of events in the USGS dataset: earthquakes, explosions, and quarry blasts. In this case, the color of the circle layer will be determined by the type of event, with a default value of blue to catch any events that do not fall into any of those categories.

Style function syntax:

let categoricalStops = [
    "earthquake": MGLStyleValue<UIColor>(rawValue: .orange),
    "explosion": MGLStyleValue(rawValue: .red),
    "quarry blast": MGLStyleValue(rawValue: .yellow),
]

layer.circleColor = MGLStyleValue(interpolationMode: .categorical,
                                  sourceStops: categoricalStops,
                                  attributeName: "type",
                                  options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .blue)])

Current syntax:

// Category type
let type = NSExpression(forConstantValue: "type")

// Categories
let earthquake = NSExpression(forConstantValue: "earthquake")
let explosion = NSExpression(forConstantValue: "explosion")
let quarryBlast = NSExpression(forConstantValue: "quarry blast")

let defaultColor = NSExpression(forConstantValue: UIColor.blue)
let orange = NSExpression(forConstantValue: UIColor.orange)
let red = NSExpression(forConstantValue: UIColor.red)
let yellow = NSExpression(forConstantValue: UIColor.yellow)

XCTExpectFailure("#331")
layer.circleColor = NSExpression(forMGLMatchingKey: type,
                             in: [earthquake:orange, explosion:red, quarryBlast:yellow],
                             default: defaultColor)

If your use case does not require a default value, you can either apply a predicate to your layer prior to styling it, or use the format string "valueForKeyPath:".

Identity

Identity interpolation mode used the attribute’s value as the style layer property value. In this example, you might set the circleRadius to the earthquake’s magnitude. In order to use a feature attribute value to style a layer property, set the property value to [NSExpression expressionForKeyPath:], which take the feature attribute name as an argument.

Style function syntax:

layer.circleRadius = MGLStyleValue(interpolationMode: .identity,
                                   sourceStops: nil,
                                   attributeName: "mag",
                                   options: [.defaultValue: MGLStyleValue<NSNumber>(rawValue: 0)])

Current syntax:

layer.circleRadius = NSExpression(forKeyPath: "mag")

identity mode

Some built-in functions can be applied to attribute values to style layer property values. To set the circle radius to three times the earthquake’s magnitude, create a multiply:by: function that takes the attribute value and the multiplier as arguments, or use a format string.

layer.circleRadius = NSExpression(forFunction: "multiply:by:", arguments: [NSExpression(forKeyPath: "mag"), 3])

multiply magnitude

You can also cast attribute values in order to use them. One example is to cast an integer as an NSString and use it as a text value.

let magnitudeLayer = MGLSymbolStyleLayer(identifier: "mag-layer", source: source)
magnitudeLayer.text = NSExpression(format: "CAST(mag, 'NSString')")
mapView.style?.addLayer(magnitudeLayer)

cast a value

Constant Values

For constant values that do not necessarily change based on camera or attribute values, use [NSExpression expressionForConstantValue:] (previously [MGLStyleValue valueWithRawValue:]).

Resources