Style symbols from mobile style file

View on GitHub

Combine multiple symbols from a mobile style file into a single symbol.

Image of style symbols from mobile style file 1 Image of style symbols from mobile style file 2

Use case

You may choose to display individual elements of a dataset like a water infrastructure network (such as valves, nodes, or endpoints) with the same basic shape, but wish to modify characteristics of elements according to some technical specifications. Multilayer symbols lets you add or remove components or modify the colors to create advanced symbol styles.

How to use the sample

Tap "Symbol" and select a symbol from each section to create a face emoji. A preview of the symbol is updated as selections are made. The color of the symbol can be set using the color picker and size can be set using the slider. Tap the map to create a point graphic using the customized emoji symbol, and tap "Clear" to clear all graphics from the display.

How it works

  1. Create a new symbol style from a stylx file using SymbolStyle(url:).
  2. Get a set of default search parameters using SymbolStyle.defaultSearchParameters and use those to retrieve a list of all symbols within the style file using SymbolStyle.searchSymbols(using:).
  3. Get the SymbolStyleSearchResults, which contains the symbols, as well as their names, keys, and categories.
  4. Use a Array of keys of the desired symbols to build a composite symbol using SymbolStyle.symbol(forKeys:).
  5. Create a Graphic using the Symbol.

Relevant API

  • MultilayerPointSymbol
  • MultilayerSymbol
  • SymbolLayer
  • SymbolStyle
  • SymbolStyleSearchParameters

Offline data

A mobile style file (created using ArcGIS Pro) provides the symbols used by the sample.

About the data

The mobile style file used in this sample was created using ArcGIS Pro, and is hosted on ArcGIS Online. It contains symbol layers that can be combined to create emojis.

Additional information

While each of these symbols can be created from scratch, a more convenient workflow is to author them using ArcGIS Pro and store them in a mobile style file (.stylx). ArcGIS Runtime can read symbols from a mobile style, and you can modify and combine them as needed in your app.

Tags

advanced symbology, mobile style, multilayer, stylx

Sample Code

StyleSymbolsFromMobileStyleFileView.swiftStyleSymbolsFromMobileStyleFileView.swiftStyleSymbolsFromMobileStyleFileView.SymbolOptionsListView.swift
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// Copyright 2023 Esri
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import ArcGIS
import SwiftUI

struct StyleSymbolsFromMobileStyleFileView: View {
    /// The display scale of the environment.
    @Environment(\.displayScale) private var displayScale

    /// The view model for the sample.
    @StateObject private var model = Model()

    /// A Boolean value that indicates whether the symbol options sheet is showing.
    @State private var isShowingSymbolOptionsSheet = false

    var body: some View {
        MapView(map: model.map, graphicsOverlays: [model.graphicsOverlay])
            .onSingleTapGesture { _, mapPoint in
                // Add the current symbol to map as a graphic at the tap location.
                let symbolGraphic = Graphic(geometry: mapPoint, symbol: model.currentSymbol?.symbol)
                model.graphicsOverlay.addGraphic(symbolGraphic)
            }
            .toolbar {
                ToolbarItemGroup(placement: .bottomBar) {
                    Button("Symbol") {
                        isShowingSymbolOptionsSheet = true
                    }
                    .sheet(isPresented: $isShowingSymbolOptionsSheet, detents: [.large]) {
                        symbolOptionsList
                    }
                    Spacer()
                    Button("Clear") {
                        // Clear all graphics from the map.
                        model.graphicsOverlay.removeAllGraphics()
                    }
                }
            }
            .task(id: displayScale) {
                // Update all the symbols when the display scale changes.
                await model.updateDisplayScale(using: displayScale)
            }
            .errorAlert(presentingError: $model.error)
    }

    /// The list containing the symbol options.
    private var symbolOptionsList: some View {
        NavigationView {
            SymbolOptionsListView(model: model)
                .navigationTitle("Symbol")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItem(placement: .confirmationAction) {
                        Button("Done") {
                            isShowingSymbolOptionsSheet = false
                        }
                    }
                }
        }
        .navigationViewStyle(.stack)
    }
}

extension StyleSymbolsFromMobileStyleFileView {
    /// The view model for the sample.
    @MainActor
    class Model: ObservableObject {
        /// A map with a topographic basemap.
        let map = Map(basemapStyle: .arcGISTopographic)

        /// The graphics overlay for all the symbol graphics on the map.
        let graphicsOverlay = GraphicsOverlay()

        /// The emoji mobile symbol style created from a local symbol style file.
        private let symbolStyle = SymbolStyle(url: .emojiMobile)

        /// The display scale of the environment used to create the symbol swatches.
        private var displayScale = 0.0

        /// The current symbol created from the symbol style based on the option selections.
        @Published private(set) var currentSymbol: SymbolDetails?

        /// The list of all the symbols and their associated data from the symbol style.
        @Published private(set) var symbolsList: [SymbolDetails] = []

        /// The current symbol option selections used to create the current symbol.
        @Published var symbolOptionSelections = SymbolOptions(
            eyes: "Eyes-crossed",
            hat: "Hat-cowboy",
            mouth: "Mouth-frown",
            color: Color.yellow,
            size: 40
        )

        /// The error shown in the error alert.
        @Published var error: Error?

        /// Updates the display scale of the current symbol and symbols list.
        func updateDisplayScale(using displayScale: Double) async {
            if displayScale != self.displayScale {
                self.displayScale = displayScale
                await updateCurrentSymbol()
                await updateSymbolsList()
            }
        }

        /// Updates the current symbol with a symbol created from the symbol style using the current option selections.
        func updateCurrentSymbol() async {
            // Get the keys from the option selections.
            var symbolKeys = ["Face1"]
            symbolKeys.append(contentsOf: symbolOptionSelections.categoryKeys.map { $0.value })

            // Get the symbol from symbol style using the keys.
            if let pointSymbol = try? await symbolStyle.symbol(forKeys: symbolKeys) as? MultilayerPointSymbol {
                // Color lock all layers but the first one.
                let layers = pointSymbol.symbolLayers
                for (i, layer) in layers.enumerated() {
                    layer.colorIsLocked = i != 0 ? true : false
                }

                pointSymbol.color = UIColor(symbolOptionSelections.color)
                pointSymbol.size = symbolOptionSelections.size

                // Create an image swatch for the symbol using the display scale.
                if let swatch = try? await pointSymbol.makeSwatch(scale: displayScale) {
                    // Update the current symbol with the created symbol and swatch.
                    currentSymbol = SymbolDetails(symbol: pointSymbol, image: swatch)
                }
            }
        }

        /// Updates the symbols list with all the symbols in the symbol style.
        private func updateSymbolsList() async {
            do {
                // Get the default symbol search parameters from the symbol style.
                let searchParameters = try await symbolStyle.defaultSearchParameters

                // Get the symbol style search results using the search parameters.
                let searchResults = try await symbolStyle.searchSymbols(using: searchParameters)

                // Create a symbol for each search result.
                let symbols = try await withThrowingTaskGroup(of: SymbolDetails.self) { group in
                    for result in searchResults {
                        group.addTask {
                            // Get the symbol from the symbol style using the symbol's key from the result.
                            let symbol = try await self.symbolStyle.symbol(forKeys: [result.key])

                            // Create an image swatch from the symbol using the display scale.
                            let swatch = try await symbol.makeSwatch(scale: self.displayScale)

                            return SymbolDetails(
                                symbol: symbol,
                                image: swatch,
                                name: result.name,
                                key: result.key,
                                category: result.category
                            )
                        }
                    }

                    var symbols: [SymbolDetails] = []
                    for try await symbol in group {
                        symbols.append(symbol)
                    }
                    return symbols
                }

                symbolsList = symbols.sorted { $0.name < $1.name }
            } catch {
                self.error = error
            }
        }
    }

    /// A symbol and its associated information.
    struct SymbolDetails {
        /// The symbol from the symbol style.
        let symbol: Symbol
        /// The image swatch of the symbol.
        let image: UIImage
        /// The name of the symbol as found in the symbol style.
        var name: String = ""
        /// The key of the symbol as found in the symbol style.
        var key: String = ""
        /// The category of the symbol as found in the symbol style.
        var category: String = ""
    }

    /// The different options used in creating a symbol from the symbol style.
    struct SymbolOptions: Equatable {
        /// A dictionary containing the key of a symbol for each symbol category.
        var categoryKeys: [SymbolCategory: String]
        /// The color of the symbol.
        var color: Color
        /// The size of the symbol.
        var size: Double

        init(eyes: String, hat: String, mouth: String, color: Color, size: Double) {
            categoryKeys = [
                .eyes: eyes,
                .hat: hat,
                .mouth: mouth
            ]
            self.color = color
            self.size = size
        }
    }

    /// The different symbol categories as found in the symbol style.
    enum SymbolCategory: String, CaseIterable {
        case eyes = "Eyes"
        case hat = "Hat"
        case mouth = "Mouth"

        /// A human-readable label of the category name.
        var label: String {
            return self.rawValue.hasSuffix("s") ? self.rawValue : "\(self.rawValue)s"
        }
    }
}

extension StyleSymbolsFromMobileStyleFileView.SymbolDetails {
    /// The human-readable label of the symbol name.
    var label: String {
        let splitName = name.replacingOccurrences(of: "-", with: " ").split(separator: " ")
        return splitName.last?.capitalized ?? name
    }
}

private extension URL {
    /// A URL to the local emoji mobile symbol style file.
    static var emojiMobile: URL {
        Bundle.main.url(forResource: "emoji-mobile", withExtension: "stylx")!
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.