Skip To Content ArcGIS for Developers Sign In Dashboard

Overview

You will learn: how to apply symbol colors and styles to features based on attribute values.

Applications can display feature layer data with different styles to enhance the visualization. The first step is to select the correct type of AGSRenderer. An AGSSimpleRenderer applies the same symbol to all features, an AGSUniqueValueRenderer applies a different symbol to each unique attribute value, and an AGSClassBreaksRenderer applies a symbol to a range of numeric values. Renderers are responsible for accessing the data and applying the appropriate symbol to each feature when the layer draws. Labels can also be displayed to show attribute information for each feature. Visit the documentation to learn more about styling layers.

In this tutorial, you will apply different renderers to enhance the visualization of the Trailheads, Trails, and Parks and Open Spaces feature layers.

Before you begin

Make sure you have installed the latest version of Xcode.

Reuse the starter project

If you have completed the Create a starter app tutorial, then copy the project into a new empty folder. Otherwise, download and unzip the project solution. Open the .xcodeproj file in Xcode. Run and verify the map displays in the device simulator.

Steps

Create an app configuration file

  1. Add a new swift file named AppConfiguration.swift. Use this file to specify constants that can be used by the app to connect to data and resources. Create four static URLs: three for accessing feature layers, and a fourth for accessing a static image for use in a picture marker symbol. You will use these resources in future steps.

    extension URL {
        static let parksAndOpenSpaces = URL(string: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Parks_and_Open_Space/FeatureServer/0")!
        static let trails = URL(string: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trails/FeatureServer/0")!
        static let trailheads = URL(string: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trailheads/FeatureServer/0")!
        static let trailheadImage = URL(string: "https://static.arcgis.com/images/Symbols/NPS/npsPictograph_0231b.png")!
    }
    

Add a layer with a unique value renderer

  1. Open ViewController.swift in the Project Navigator. Add a new method named addLayerWithUniqueValueRenderer. Use this method to apply a different symbol for each type of park area to the Parks and Open Spaces feature layer.

    /** ADD **/
    private func addLayerWithUniqueValueRenderer() {
        // Create a parks and open spaces feature layer.
        let parksAndOpenSpaces: AGSFeatureLayer = {
            let table = AGSServiceFeatureTable(url: .parksAndOpenSpaces)
            return AGSFeatureLayer(featureTable: table)
        }()
    
        // Create a unique value for natural areas.
        let naturalAreas: AGSUniqueValue = {
            // Create a purple fill symbol.
            let symbol = AGSSimpleFillSymbol(
                style: .solid,
                color: .purple,
                outline: .none
            )
            // Create a unique value provided a value and a symbol.
            return AGSUniqueValue(
                description: "Natural Areas",
                label: "Natural Areas",
                symbol: symbol,
                values: [ "Natural Areas" ]
            )
        }()
    
        // Create a unique value for regional open spaces.
        let regionalOpenSpace: AGSUniqueValue = {
            // Create a green fill symbol.
            let symbol = AGSSimpleFillSymbol(
                style: .solid,
                color: .green,
                outline: .none
            )
            // Create a unique value provided a value and a symbol.
            return AGSUniqueValue(
                description: "Regional Open Space",
                label: "Regional Open Space",
                symbol: symbol,
                values: ["Regional Open Space"]
            )
        }()
    
        // Create a unique value for local parks.
        let localPark: AGSUniqueValue = {
            // Create a blue fill symbol.
            let symbol = AGSSimpleFillSymbol(
                style: .solid,
                color: .blue,
                outline: .none
            )
            // Create a unique value provided a value and a symbol.
            return AGSUniqueValue(
                description: "Local Park",
                label: "Local Park",
                symbol: symbol,
                values: ["Local Park"]
            )
        }()
    
        // Create a unique value for regional recreation parks.
        let regionalRecreationPark: AGSUniqueValue = {
            // Create a red fill symbol.
            let symbol = AGSSimpleFillSymbol(
                style: .solid,
                color: .red,
                outline: .none
            )
            // Create a unique value provided a value and a symbol.
            return AGSUniqueValue(
                description: "Regional Recreation Park",
                label: "Regional Recreation Park",
                symbol: symbol,
                values: ["Regional Recreation Park"]
            )
        }()
    
        // Create and assign a unique value renderer to the feature layer.
        parksAndOpenSpaces.renderer = AGSUniqueValueRenderer(
            fieldNames: ["TYPE"],
            uniqueValues: [
                naturalAreas,
                regionalOpenSpace,
                localPark,
                regionalRecreationPark
            ],
            defaultLabel: "Open Spaces",
            defaultSymbol: .none
        )
    
        // Set the layer opacity to semi-transparent.
        parksAndOpenSpaces.opacity = 0.2
    
        // Add parks and open spaces layer to map.
        mapView.map?.operationalLayers.add(parksAndOpenSpaces)
    }
    
  2. Update viewDidLoad to call the new addLayerWithUniqueValueRenderer method.

    override func viewDidLoad() {
        super.viewDidLoad()
        setupMap()
        /** ADD **/
        addLayerWithUniqueValueRenderer()
    }
    
  3. Press Command-R to run the app in the iOS Simulator. When the app opens, you should see the Parks and Open Spaces feature layer added to the map. The map will draw the different types of parks and open spaces with four unique symbols.

    (Note, as of 100.8 Runtime supports Metal. In order to run your app in a simulator you must meet some minimum requirements. You must be developing on macOS Catalina, using Xcode 11, and simulating iOS 13.)

Add a layer with a class breaks renderer

  1. Add a new method named addLayerWithClassBreaksRenderer. Use this method to apply a different symbol for each one of five ranges of elevation gain to the Trails feature layer.

    private func addLayerWithClassBreaksRenderer() {
        // Create a trails feature layer.
        let trailsLayer: AGSFeatureLayer = {
            let table = AGSServiceFeatureTable(url: .trails)
            return AGSFeatureLayer(featureTable: table)
        }()
    
        // Create the first class break.
        let firstClassBreak: AGSClassBreak = {
            // Create a line symbol with 3.0 pt width.
            let symbol = AGSSimpleLineSymbol(
                style: .solid,
                color: .purple,
                width: 3.0
            )
            // Create a class break provided min/max values and a symbol.
            return AGSClassBreak(
                description: "Under 500",
                label: "0 - 500",
                minValue: 0.0,
                maxValue: 500.0,
                symbol: symbol
            )
        }()
    
        // Create the second class break.
        let secondClassBreak: AGSClassBreak = {
            // Create a line symbol with 4.0 pt width.
            let symbol = AGSSimpleLineSymbol(
                style: .solid,
                color: .purple,
                width: 4.0
            )
            // Create a class break provided min/max values and a symbol.
            return AGSClassBreak(
                description: "501 to 1000",
                label: "501 - 1000",
                minValue: 501.0,
                maxValue: 1000.0,
                symbol: symbol
            )
        }()
    
        // Create the third class break.
        let thirdClassBreak: AGSClassBreak = {
            // Create a line symbol with 5.0 pt width.
            let symbol = AGSSimpleLineSymbol(
                style: .solid,
                color: .purple,
                width: 5.0
            )
            // Create a class break provided min/max values and a symbol.
            return AGSClassBreak(
                description: "1001 to 1500",
                label: "1001 - 1500",
                minValue: 1001.0,
                maxValue: 1500.0,
                symbol: symbol
            )
        }()
    
        // Create the fourth class break.
        let fourthClassBreak: AGSClassBreak = {
            // Create a line symbol with 6.0 pt width.
            let symbol = AGSSimpleLineSymbol(
                style: .solid,
                color: .purple,
                width: 6.0
            )
            // Create a class break provided min/max values and a symbol.
            return AGSClassBreak(
                description: "1501 to 2000",
                label: "1501 - 2000",
                minValue: 1201.0,
                maxValue: 2000.0,
                symbol: symbol
            )
        }()
    
        // Create the fifth class break.
        let fifthClassBreak: AGSClassBreak = {
            // Create a line symbol with 7.0 pt width.
            let symbol = AGSSimpleLineSymbol(
                style: .solid,
                color: .purple,
                width: 7.0
            )
            // Create a class break provided min/max values and a symbol.
            return AGSClassBreak(
                description: "2001 to 2300",
                label: "2001 - 2300",
                minValue: 2001.0,
                maxValue: 2300.0,
                symbol: symbol
            )
        }()
    
        // Create and assign a class breaks renderer to the feature layer.
        trailsLayer.renderer = AGSClassBreaksRenderer(
            fieldName: "ELEV_GAIN",
            classBreaks: [
                firstClassBreak,
                secondClassBreak,
                thirdClassBreak,
                fourthClassBreak,
                fifthClassBreak
            ]
        )
    
        // Set the layer opacity to semi-transparent.
        trailsLayer.opacity = 0.75
    
        // Add trails layer to map.
        mapView.map?.operationalLayers.add(trailsLayer)
    }
    
  2. Update viewDidLoad to call the new addLayerWithClassBreaksRenderer method.

    override func viewDidLoad() {
        super.viewDidLoad()
        setupMap()
        addLayerWithUniqueValueRenderer()
        /** ADD **/
        addLayerWithClassBreaksRenderer()
    }
    
  3. Press Command-R to run the app in the iOS Simulator. When the app opens, you should see the Trails feature layer added to the map. The map will draw trails with different symbols depending on the trail's elevation gain.

Add layers with definition expressions

  1. Add a new method named addLayersWithDefinitionExpressions. Use this method to apply a symbol to only a subset of features in the Trails feature layer. Create two feature layer subsets: one that permits bike usage on the trail, and another that does not.

    private func addLayersWithDefinitionExpressions() {
    
        // MARK: Bikes Not Permitted
    
        // Create a trails feature layer.
        let bikesNotPermittedLayer: AGSFeatureLayer = {
            let table = AGSServiceFeatureTable(url: .trails)
            return AGSFeatureLayer(featureTable: table)
        }()
    
        // Write a definition expression to filter for trails that don't permit the use of bikes.
        bikesNotPermittedLayer.definitionExpression = "USE_BIKE = 'NO'"
    
        // Create and assign a simple renderer to the feature layer.
        bikesNotPermittedLayer.renderer = {
            let symbol = AGSSimpleLineSymbol(
                style: .dot,
                color: .red,
                width: 2.0
            )
            return AGSSimpleRenderer(symbol: symbol)
        }()
    
        // Add bikes not permitted layer to map.
        mapView.map?.operationalLayers.add(bikesNotPermittedLayer)
    
        // MARK: Bikes Permitted
    
        // Create another trails feature layer.
        let bikesPermittedLayer: AGSFeatureLayer = {
            let table = AGSServiceFeatureTable(url: .trails)
            return AGSFeatureLayer(featureTable: table)
        }()
    
        // Write a definition expression to filter for trails that do permit the use of bikes.
        bikesPermittedLayer.definitionExpression = "USE_BIKE = 'Yes'"
    
        // Create and assign a simple renderer to the feature layer.
        bikesPermittedLayer.renderer = {
            let symbol = AGSSimpleLineSymbol(
                style: .dot,
                color: .blue,
                width: 2.0
            )
            return AGSSimpleRenderer(symbol: symbol)
        }()
    
        // Add bikes permitted layer to map.
        mapView.map?.operationalLayers.add(bikesPermittedLayer)
    }
    
  2. Update viewDidLoad to call the new addLayersWithDefinitionExpressions method.

    override func viewDidLoad() {
        super.viewDidLoad()
        setupMap()
        addLayerWithUniqueValueRenderer()
        addLayerWithClassBreaksRenderer()
        /** ADD **/
        addLayersWithDefinitionExpressions()
    }
    
  3. Press Command-R to run the app in the iOS Simulator. When the app opens, you should see the Trails feature layer added to the map. The map will draw trails with different symbols depending if bikes are permitted.

Add a layer with a label definition

  1. Add a method named addLayerWithLabelDefinition. Use this method to style trailheads with hiker images and labels for the Trailheads feature layer. Use an AGSPictureMarkerSymbol to draw a trailhead hiker image. Use an AGSLabelDefinition to label a trailhead by its name.

    private func addLayerWithLabelDefinition() {
    
        let trailheadsLayer: AGSFeatureLayer = {
            let table = AGSServiceFeatureTable(url: .trailheads)
            return AGSFeatureLayer(featureTable: table)
        }()
    
        trailheadsLayer.renderer = {
            let pictureMarkerSymbol = AGSPictureMarkerSymbol(url: .trailheadImage)
            pictureMarkerSymbol.height = 18.0
            pictureMarkerSymbol.width = 18.0
            return AGSSimpleRenderer(symbol: pictureMarkerSymbol)
        }()
    
        func makeTrailheadsLabelDefinition() throws -> AGSLabelDefinition {
    
            let trailHeadsTextSymbol: AGSTextSymbol = {
                let symbol = AGSTextSymbol()
                symbol.color = .white
                symbol.size = 12.0
                symbol.haloColor = .green
                symbol.haloWidth = 2.0
                symbol.fontFamily = "Noto Sans"
                symbol.fontStyle = .italic
                symbol.fontWeight = .normal
                return symbol
            }()
    
            let labelJSON: [String: Any] = [
                "labelExpressionInfo": [
                    "expression": "$feature.TRL_NAME"
                ],
                "labelPlacement": "above-center",
                "symbol": try trailHeadsTextSymbol.toJSON()
            ]
    
            return try AGSLabelDefinition.fromJSON(labelJSON) as! AGSLabelDefinition
        }
    
        trailheadsLayer.labelsEnabled = true
    
        do {
            let trailsHeadsLabelDefinition = try makeTrailheadsLabelDefinition()
            trailheadsLayer.labelDefinitions.addObjects(from: [trailsHeadsLabelDefinition])
        }
        catch {
            print(error)
        }
    
        // Add trailheads layer to map.
        mapView.map?.operationalLayers.add(trailheadsLayer)
    }
    
  2. Update viewDidLoad to call the new addLayerWithLabelDefinition method.

    override func viewDidLoad() {
        super.viewDidLoad()
        setupMap()
        addLayerWithUniqueValueRenderer()
        addLayerWithClassBreaksRenderer()
        addLayersWithDefinitionExpressions()
        /** ADD **/
        addLayerWithLabelDefinition()
    }
    
  3. Press Command-R to run the app in the iOS Simulator. When the app opens, you should see the Trails feature layer added to the map. The map will draw a hiker icon for trails and display the trail's name.

Congratulations, you're done!

Run your app to test your code. When the app opens, you should see trailheads symbolized with a picture marker, trails symbolized according to accessibility by bikes, and open spaces according to acreage.

Check out and compare with our completed solution project.

Challenge

Explore multiple unique values

An AGSUniqueValueRenderer can use more than one field to define unique values to display. The Trails layer was rendered with values in USE_BIKE. The feature table, however, has additional fields to describe if the trail allows pets (PET_ACC), horses (USE_EQU), or all-terrain vehicles (USE_ATV). Experiment with rendering the trails layer with uniqueValues that are combinations of values from these attributes (trails that allow both bikes and pets, for example).