Show grid

View on GitHub

Display coordinate system grids including latitude-longitude, MGRS, UTM and USNG on a map view. Also, toggle label visibility and change the color of grid lines and grid labels.

Image of Show grid sample

Use case

Grids are often used on printed maps, but can also be helpful on digital maps, to identify locations on a map.

How to use the sample

Tap the button in the toolbar to open the grid settings view. You can select type of grid from grid types (LatLong, MGRS, UTM and USNG) and modify its properties like grid visibility, grid color, label visibility, label color, label position, label format and label unit.

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.
  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 using the grid(_:) modifier.

Relevant API

  • Grid
  • LatitudeLongitudeGrid
  • MapView
  • MGRSGrid
  • 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
// 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()

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

    var body: some View {
        MapView(map: model.map)
            .grid(model.grid)
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button("Grid Settings") {
                        showsGridSettingsView = true
                    }
                    .popover(isPresented: $showsGridSettingsView) {
                        NavigationStack {
                            GridSettingsView(model: model)
                        }
                        .presentationDetents([.fraction(0.6), .large])
                        .frame(idealWidth: 350, idealHeight: 480)
                    }
                }
            }
    }
}

private extension ShowGridView {
    // MARK: - Model

    /// The view model for the sample.
    final class Model: ObservableObject {
        /// A map with topographic basemap.
        let map: Map = {
            let map = Map(basemapStyle: .arcGISTopographic)
            map.initialViewpoint = Viewpoint(latitude: 34.05, longitude: -118.25, scale: 8e6)
            return map
        }()

        /// The map 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

        /// 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 = grid.labelPosition

            return newGrid
        }
    }

    // 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)
                        }
                    }

                    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 in a map 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")
        }
    }
}

// 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.