Show grid

View on GitHub

Display and customize coordinate system grids, including Latitude/Longitude, MGRS, UTM, and USNG, on a map view or scene view.

Image of Show grid sample

Use case

Grids are often used on printed maps, but can also be helpful on digital 2D maps or 3D scenes, to identify locations.

How to use the sample

Use the picker to change the view from 2D or 3D, or tap the button in the toolbar to open the grid settings. You can select the type of grid (LatLong, MGRS, UTM, and USNG) and modify its properties like the visibility and color of the lines, and the position, format, and units of the labels.

How it works

  1. Create an instance of one of the Grid types.
  2. Grid lines and labels can be styled per grid level with grid.lineSymbols[0] and grid.textSymbols[0] subscripts on the grid.
  3. The label position, format, unit, and visibility can be specified with labelPosition, labelFormat, labelUnit, and isVisible on the Grid.
    • Note that as of 200.6, MGRS, UTM, and USNG grids in a SceneView only support the geographic label position.
  4. For the LatitudeLongitudeGrid type, you can specify a label format of decimalDegrees or degreesMinutesSeconds.
  5. To set the grid, assign it to the map view or scene view using the grid(_:) modifier.

Relevant API

  • Grid
  • LatitudeLongitudeGrid
  • MapView
  • MGRSGrid
  • SceneView
  • SimpleLineSymbol
  • TextSymbol
  • USNGGrid
  • UTMGrid

Tags

coordinates, degrees, graticule, grid, latitude, longitude, MGRS, minutes, seconds, USNG, UTM

Sample Code

ShowGridView.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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
// Copyright 2024 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 ShowGridView: View {
    /// The view model for the sample.
    @StateObject private var model = Model()

    /// The current viewpoint of the geo views.
    @State private var viewpoint = Viewpoint(latitude: 34.05, longitude: -118.25, scale: 8e6)

    /// A Boolean value indicating whether the settings view should be presented.
    @State private var showsGridSettingsView = false

    var body: some View {
        Group {
            switch model.geoViewType {
            case .mapView:
                MapView(map: model.map, viewpoint: viewpoint)
                    .grid(model.grid)
                    .onViewpointChanged(kind: .centerAndScale) { viewpoint = $0 }
            case .sceneView:
                SceneView(scene: model.scene, viewpoint: viewpoint)
                    .grid(model.grid)
                    .onViewpointChanged(kind: .centerAndScale) { viewpoint = $0 }
            }
        }
        .toolbar {
            ToolbarItemGroup(placement: .bottomBar) {
                Button("Grid Settings") {
                    showsGridSettingsView = true
                }
                .popover(isPresented: $showsGridSettingsView) {
                    NavigationStack {
                        GridSettingsView(model: model)
                    }
                    .presentationDetents([.fraction(0.6), .large])
                    .frame(idealWidth: 350, idealHeight: 480)
                }

                Picker("Geo View", selection: $model.geoViewType) {
                    Text("Map View").tag(GeoViewType.mapView)
                    Text("Scene View").tag(GeoViewType.sceneView)
                }
            }
        }
    }
}

private extension ShowGridView {
    // MARK: - Model

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

        /// A scene with elevation and a topographic basemap.
        let scene: ArcGIS.Scene = {
            let scene = Scene(basemapStyle: .arcGISTopographic)
            let elevationSource = ArcGISTiledElevationSource(url: .worldElevationService)
            scene.baseSurface.addElevationSource(elevationSource)
            return scene
        }()

        /// The type of geo view that is showing.
        @Published var geoViewType = GeoViewType.mapView {
            didSet { grid = makeGrid(type: gridType) }
        }

        /// The geo view's grid, initially set to a Lat-Lon grid.
        @Published var grid: ArcGIS.Grid = LatitudeLongitudeGrid()

        /// The kind of grid to display.
        @Published var gridType: GridType = .latitudeLongitude {
            didSet { grid = makeGrid(type: gridType) }
        }

        /// The format used for labeling the grid.
        @Published var labelFormat: LatitudeLongitudeGrid.LabelFormat = .decimalDegrees

        /// The units used for labeling the USNG grid.
        @Published var usngLabelUnit: USNGGrid.LabelUnit = .kilometersMeters

        /// The units used for labeling the MGRS grid.
        @Published var mgrsLabelUnit: MGRSGrid.LabelUnit = .kilometersMeters

        /// A Boolean value indicating whether the current grid only supports `LabelPosition.geographic`.
        var gridOnlySupportsGeographic: Bool {
            geoViewType == .sceneView && gridType != .latitudeLongitude
        }

        /// Creates a new grid of a given type.
        /// - Parameter gridType: The kind of grid to make.
        /// - Returns: A new `Grid` object.
        private func makeGrid(type gridType: GridType) -> ArcGIS.Grid {
            let newGrid: ArcGIS.Grid
            switch gridType {
            case .latitudeLongitude:
                let latitudeLongitudeGrid = LatitudeLongitudeGrid()
                latitudeLongitudeGrid.labelFormat = labelFormat
                newGrid = latitudeLongitudeGrid
            case .mgrs:
                let mgrsGrid = MGRSGrid()
                mgrsGrid.labelUnit = mgrsLabelUnit
                newGrid = mgrsGrid
            case .usng:
                let usngGrid = USNGGrid()
                usngGrid.labelUnit = usngLabelUnit
                newGrid = usngGrid
            case .utm:
                newGrid = UTMGrid()
            }

            newGrid.isVisible = grid.isVisible
            newGrid.labelsAreVisible = grid.labelsAreVisible
            newGrid.linesColor = grid.linesColor
            newGrid.labelsColor = grid.labelsColor
            newGrid.labelPosition = gridOnlySupportsGeographic ? .geographic : grid.labelPosition

            return newGrid
        }
    }

    /// A type of `GeoView`.
    enum GeoViewType {
        case mapView, sceneView
    }

    // MARK: - Settings View

    struct GridSettingsView: View {
        /// The action to dismiss the sheet.
        @Environment(\.dismiss) private var dismiss

        /// The view model for the sample.
        @ObservedObject var model: Model

        var body: some View {
            Form {
                Section("Grid Line Settings") {
                    Picker("Grid Type", selection: $model.gridType) {
                        ForEach(GridType.allCases, id: \.self) { type in
                            Text(type.label)
                        }
                    }

                    Toggle("Visible", isOn: $model.grid.isVisible)

                    ColorPicker("Color", selection: $model.grid.linesColor)
                }

                Section("Labels Settings") {
                    Toggle("Visible", isOn: $model.grid.labelsAreVisible)

                    ColorPicker("Color", selection: $model.grid.labelsColor)

                    Picker("Position", selection: $model.grid.labelPosition) {
                        ForEach(Grid.LabelPosition.allCases, id: \.self) { position in
                            Text(position.label)
                        }
                    }
                    .disabled(model.gridOnlySupportsGeographic)

                    if let latitudeLongitudeGrid = model.grid as? LatitudeLongitudeGrid {
                        Picker("Format", selection: $model.labelFormat) {
                            ForEach(LatitudeLongitudeGrid.LabelFormat.allCases, id: \.self) { format in
                                Text(format.label)
                            }
                        }
                        .onChange(of: model.labelFormat) { newLabelFormat in
                            latitudeLongitudeGrid.labelFormat = newLabelFormat
                        }
                    } else if let mgrsGrid = model.grid as? MGRSGrid {
                        Picker("Unit", selection: $model.mgrsLabelUnit) {
                            ForEach(MGRSGrid.LabelUnit.allCases, id: \.self) { unit in
                                Text(unit.label)
                            }
                        }
                        .onChange(of: model.mgrsLabelUnit) { newMGRSLabelUnit in
                            mgrsGrid.labelUnit = newMGRSLabelUnit
                        }
                    } else if let usngGrid = model.grid as? USNGGrid {
                        Picker("Unit", selection: $model.usngLabelUnit) {
                            ForEach(USNGGrid.LabelUnit.allCases, id: \.self) { unit in
                                Text(unit.label)
                            }
                        }
                        .onChange(of: model.usngLabelUnit) { newUSNGLabelUnit in
                            usngGrid.labelUnit = newUSNGLabelUnit
                        }
                    }
                }
            }
            .navigationTitle("Grid Settings")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done") { dismiss() }
                }
            }
        }
    }
}

// MARK: - Helper Extensions

private extension ArcGIS.Grid {
    /// The color of the grid lines.
    var linesColor: Color {
        get {
            let lineSymbol = lineSymbols.first(where: { $0 is LineSymbol }) as! LineSymbol
            return Color(uiColor: lineSymbol.color)
        }
        set {
            for symbol in lineSymbols where symbol is LineSymbol {
                let lineSymbol = symbol as! LineSymbol
                lineSymbol.color = UIColor(newValue)
            }
        }
    }

    /// The color of the grid labels.
    var labelsColor: Color {
        get {
            let textSymbol = textSymbols.first(where: { $0 is TextSymbol }) as! TextSymbol
            return Color(uiColor: textSymbol.color)
        }
        set {
            for symbol in textSymbols where symbol is TextSymbol {
                let textSymbol = symbol as! TextSymbol
                textSymbol.color = UIColor(newValue)
            }
        }
    }
}

private extension ShowGridView {
    /// The kinds of grid to show on the geo view.
    enum GridType: CaseIterable {
        case latitudeLongitude, mgrs, usng, utm

        var label: String {
            switch self {
            case .latitudeLongitude: "Latitude-Longitude"
            case .mgrs: "MGRS"
            case .usng: "USNG"
            case .utm: "UTM"
            }
        }
    }
}

private extension ArcGIS.Grid.LabelPosition {
    static var allCases: [Self] {
        return [
            .allSides,
            .center,
            .topLeft,
            .topRight,
            .bottomLeft,
            .bottomRight,
            .geographic
        ]
    }

    var label: String {
        switch self {
        case .geographic: "Geographic"
        case .bottomLeft: "Bottom Left"
        case .bottomRight: "Bottom Right"
        case .topLeft: "Top Left"
        case .topRight: "Top Right"
        case .center: "Center"
        case .allSides: "All Sides"
        @unknown default: fatalError("Unknown grid label position")
        }
    }
}

private extension LatitudeLongitudeGrid.LabelFormat {
    static var allCases: [Self] { [.decimalDegrees, .degreesMinutesSeconds] }

    var label: String {
        switch self {
        case .decimalDegrees: "Decimal Degrees"
        case .degreesMinutesSeconds: "Degrees, Minutes, Seconds"
        @unknown default: fatalError("Unknown Lat-Lon grid label format")
        }
    }
}

private extension MGRSGrid.LabelUnit {
    static var allCases: [Self] { [.kilometersMeters, .meters] }

    var label: String {
        switch self {
        case .kilometersMeters: "Kilometers or Meters"
        case .meters: "Meters"
        @unknown default: fatalError("Unknown MGRS grid label unit")
        }
    }
}

private extension USNGGrid.LabelUnit {
    static var allCases: [Self] { [.kilometersMeters, .meters] }

    var label: String {
        switch self {
        case .kilometersMeters: "Kilometers or Meters"
        case .meters: "Meters"
        @unknown default: fatalError("Unknown USNG grid label unit")
        }
    }
}

private extension URL {
    /// A web URL to the Terrain3D image server on ArcGIS REST.
    static var worldElevationService: URL {
        URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!
    }
}

// MARK: - Preview

#Preview {
    ShowGridView()
}

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