Display grid

View on GitHub
Sample viewer app

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 display grid

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 on the "Change Grid" button in the toolbar to open the settings view. You can select type of grid from "Grid Type" (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 AGSGrid types.
  2. Grid lines and labels can be styled per grid level with AGSGrid.setLineSymbol(_:forLevel:) and AGSGrid.setTextSymbol(_:forLevel:) methods on the grid.
  3. The label position, format, unit and visibility can be specified with labelPosition, labelFormat, labelUnit and isVisible on the AGSGrid.
  4. For the AGSLatitudeLongitudeGrid type, you can specify a label format of AGSLatitudeLongitudeGridLabelFormat.decimalDegrees or AGSLatitudeLongitudeGridLabelFormat.degreesMinutesSeconds.
  5. To set the grid, assign it to the map view's grid property.

Relevant API

  • AGSGrid
  • AGSLatitudeLongitudeGrid
  • AGSMapView
  • AGSMGRSGrid
  • AGSSimpleLineSymbol
  • AGSTextSymbol
  • AGSUSNGGrid
  • AGSUTMGrid

Tags

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

Sample Code

DisplayGridSettingsViewController.swiftDisplayGridViewController.swift
                                                                                                                                                                                                                                                                                                                                                        
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
342
343
344
//
// Copyright 2017 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
//
//   http://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 UIKit
import ArcGIS

class DisplayGridSettingsViewController: UITableViewController {
    var mapView: AGSMapView? {
        didSet {
            if isViewLoaded {
                updateUIForGrid()
            }
        }
    }

    private let labelPositionLabels = ["Geographic", "Bottom Left", "Bottom Right", "Top Left", "Top Right", "Center", "All Sides"]
    private let labelUnitLabels = ["Kilometers Meters", "Meters"]
    private let labelFormatLabels = ["Decimal Degrees", "Degrees Minutes Seconds"]

    @IBOutlet private weak var gridVisibilitySwitch: UISwitch?
    @IBOutlet private weak var labelVisibilitySwitch: UISwitch?

    @IBOutlet private weak var gridTypeCell: UITableViewCell?
    @IBOutlet private weak var gridColorCell: UITableViewCell?

    @IBOutlet private weak var labelFormatCell: UITableViewCell?
    @IBOutlet private weak var labelUnitCell: UITableViewCell?
    @IBOutlet private weak var labelPositionCell: UITableViewCell?
    @IBOutlet private weak var labelColorCell: UITableViewCell?

    @IBOutlet private weak var gridColorSwatchView: UIView?
    @IBOutlet private weak var labelColorSwatchView: UIView?

    private enum GridType: Int, CaseIterable {
        case latLong, mgrs, utm, usng

        init?(grid: AGSGrid) {
            switch grid {
            case is AGSLatitudeLongitudeGrid: self = .latLong
            case is AGSMGRSGrid: self = .mgrs
            case is AGSUTMGrid: self = .utm
            case is AGSUSNGGrid: self = .usng
            default: return nil
            }
        }

        var label: String {
            switch self {
            case .latLong: return "LatLong"
            case .mgrs: return "MGRS"
            case .utm: return "UTM"
            case .usng: return "USNG"
            }
        }
    }

    private func makeGrid(type: GridType) -> AGSGrid {
        switch type {
        case .latLong: return AGSLatitudeLongitudeGrid()
        case .mgrs: return AGSMGRSGrid()
        case .utm: return AGSUTMGrid()
        case .usng: return AGSUSNGGrid()
        }
    }

    // MARK: - View Methods

    override func viewDidLoad() {
        super.viewDidLoad()

        // set corner radius and border for color swatches
        for swatch in [gridColorSwatchView, labelColorSwatchView] {
            swatch?.layer.cornerRadius = 5
            swatch?.layer.borderColor = UIColor(hue: 0, saturation: 0, brightness: 0.9, alpha: 1).cgColor
            swatch?.layer.borderWidth = 1
        }

        // Setup UI Controls
        updateUIForGrid()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // set the colors redundantly to avoid a visual glitch when closing the color picker
        updateUIForGridColor()
        updateUIForLabelColor()
    }

    private func updateUIForGrid() {
        // Set current grid type
        guard let grid = mapView?.grid,
            let gridType = GridType(grid: grid) else {
            return
        }

        gridTypeCell?.detailTextLabel?.text = gridType.label

        gridVisibilitySwitch?.isOn = grid.isVisible
        labelVisibilitySwitch?.isOn = grid.labelVisibility

        labelPositionCell?.detailTextLabel?.text = labelPositionLabels[grid.labelPosition.rawValue]

        updateLabelFormatUI()
        updateLabelUnitUI()
        updateUIForGridColor()
        updateUIForLabelColor()
    }

    private func updateLabelFormatUI() {
        if let grid = mapView?.grid as? AGSLatitudeLongitudeGrid {
            labelFormatCell?.detailTextLabel?.text = labelFormatLabels[grid.labelFormat.rawValue]
            labelFormatCell?.detailTextLabel?.isEnabled = true
            labelFormatCell?.selectionStyle = .default
        } else {
            labelFormatCell?.detailTextLabel?.text = "N/A"
            labelFormatCell?.detailTextLabel?.isEnabled = false
            labelFormatCell?.selectionStyle = .none
        }
    }

    private func updateLabelUnitUI() {
        if let grid = mapView?.grid,
            let labelUnitID = (grid as? AGSMGRSGrid)?.labelUnit.rawValue ?? (grid as? AGSUSNGGrid)?.labelUnit.rawValue {
            labelUnitCell?.detailTextLabel?.text = labelUnitLabels[labelUnitID]
            labelUnitCell?.detailTextLabel?.isEnabled = true
            labelUnitCell?.selectionStyle = .default
        } else {
            labelUnitCell?.detailTextLabel?.text = "N/A"
            labelUnitCell?.detailTextLabel?.isEnabled = false
            labelUnitCell?.selectionStyle = .none
        }
    }

    private func updateUIForGridColor() {
        if let grid = mapView?.grid {
            gridColorSwatchView?.backgroundColor = gridColor(of: grid)
        }
    }

    private func updateUIForLabelColor() {
        if let grid = mapView?.grid {
            labelColorSwatchView?.backgroundColor = labelColor(of: grid)
        }
    }

    // MARK: - Helpers

    /// Creates a new grid object based on the type, applies the common configuration
    /// from the existing grid, and adds it to the map view.
    private func changeGrid(to newGridType: GridType) {
        guard let displayedGrid = mapView?.grid,
            // don't replace the grid if it already has the target type
            GridType(grid: displayedGrid) != newGridType else {
            return
        }

        // create a new grid object based on the type
        let newGrid = makeGrid(type: newGridType)

        // apply the common settings of the exiting grid to the new grid
        newGrid.labelPosition = displayedGrid.labelPosition
        newGrid.labelVisibility = displayedGrid.labelVisibility
        newGrid.isVisible = displayedGrid.isVisible
        if let gridColor = gridColor(of: displayedGrid) {
            changeGridColor(of: newGrid, to: gridColor)
        }
        if let labelColor = labelColor(of: displayedGrid) {
            changeLabelColor(of: newGrid, to: labelColor)
        }

        // set the newly-created grid as the map view's grid
        mapView?.grid = newGrid

        // update the UI in case the
        updateUIForGrid()
    }

    // MARK: - Actions

    @IBAction func gridVisibilityAction(_ sender: UISwitch) {
        mapView?.grid?.isVisible = sender.isOn
    }

    @IBAction func labelVisibilityAction(_ sender: UISwitch) {
        mapView?.grid?.labelVisibility = sender.isOn
    }

    // MARK: - Colors

    private func gridColor(of grid: AGSGrid) -> UIColor? {
        guard let lineSymbol = grid.lineSymbol(forLevel: 0) as? AGSLineSymbol else {
            return nil
        }
        return lineSymbol.color
    }

    private func labelColor(of grid: AGSGrid) -> UIColor? {
        guard let textSymbol = grid.textSymbol(forLevel: 0) as? AGSTextSymbol else {
            return nil
        }
        return textSymbol.color
    }

    /// Changes the grid color.
    private func changeGridColor(of grid: AGSGrid, to color: UIColor) {
        for gridLevel in 0..<grid.levelCount {
            let lineSymbol = AGSSimpleLineSymbol(style: .solid, color: color, width: CGFloat(gridLevel + 1))
            grid.setLineSymbol(lineSymbol, forLevel: gridLevel)
        }
    }

    /// Changes the grid label color.
    private func changeLabelColor(of grid: AGSGrid, to color: UIColor) {
        for gridLevel in 0..<grid.levelCount {
            let textSymbol = AGSTextSymbol()
            textSymbol.color = color
            textSymbol.size = 14
            textSymbol.horizontalAlignment = .left
            textSymbol.verticalAlignment = .bottom
            textSymbol.haloColor = .white
            textSymbol.haloWidth = CGFloat(gridLevel + 1)
            grid.setTextSymbol(textSymbol, forLevel: gridLevel)
        }
    }

    // MARK: - UITableViewDelegate

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        switch tableView.cellForRow(at: indexPath) {
        case gridTypeCell:
            showGridTypePicker()
        case labelPositionCell:
            showLabelPositionPicker()
        case labelFormatCell:
            showLabelFormatPicker()
        case labelUnitCell:
            showLabelUnitPicker()
        case gridColorCell:
            showGridColorPicker()
        case labelColorCell:
            showLabelColorPicker()
        default:
            break
        }
    }

    private func showLabelPositionPicker() {
        guard let grid = mapView?.grid else {
            return
        }
        let selectedIndex = grid.labelPosition.rawValue
        let optionsViewController = OptionsTableViewController(labels: labelPositionLabels, selectedIndex: selectedIndex) { (newIndex) in
            self.mapView?.grid?.labelPosition = AGSGridLabelPosition(rawValue: newIndex)!
        }
        optionsViewController.title = "Position"
        show(optionsViewController, sender: self)
    }

    private func showLabelFormatPicker() {
        guard let grid = mapView?.grid,
            let selectedIndex = (grid as? AGSLatitudeLongitudeGrid)?.labelFormat.rawValue else {
            return
        }
        let optionsViewController = OptionsTableViewController(labels: labelFormatLabels, selectedIndex: selectedIndex) { (newIndex) in
            if let grid = self.mapView?.grid as? AGSLatitudeLongitudeGrid {
                grid.labelFormat = AGSLatitudeLongitudeGridLabelFormat(rawValue: newIndex)!
                self.updateLabelFormatUI()
            }
        }
        optionsViewController.title = "Format"
        show(optionsViewController, sender: self)
    }

    private func showLabelUnitPicker() {
        guard let grid = mapView?.grid,
            let selectedIndex = (grid as? AGSUSNGGrid)?.labelUnit.rawValue ?? (grid as? AGSMGRSGrid)?.labelUnit.rawValue else {
            return
        }
        let optionsViewController = OptionsTableViewController(labels: labelUnitLabels, selectedIndex: selectedIndex) { (newIndex) in
            if let grid = self.mapView?.grid as? AGSMGRSGrid {
                grid.labelUnit = AGSMGRSGridLabelUnit(rawValue: newIndex)!
            } else if let grid = self.mapView?.grid as? AGSUSNGGrid {
                grid.labelUnit = AGSUSNGGridLabelUnit(rawValue: newIndex)!
            }
            self.updateLabelUnitUI()
        }
        optionsViewController.title = "Unit"
        show(optionsViewController, sender: self)
    }

    private func showGridTypePicker() {
        guard let grid = mapView?.grid,
            let gridType = GridType(grid: grid) else {
            return
        }
        let selectedIndex = gridType.rawValue
        let labels = GridType.allCases.map { (type) -> String in
            return type.label
        }
        let optionsViewController = OptionsTableViewController(labels: labels, selectedIndex: selectedIndex) { (newIndex) in
            self.changeGrid(to: GridType(rawValue: newIndex)!)
        }
        optionsViewController.title = "Grid Type"
        show(optionsViewController, sender: self)
    }

    private func showGridColorPicker() {
        guard let grid = mapView?.grid,
            let color = gridColor(of: grid) else {
            return
        }
        let controller = ColorPickerViewController.instantiateWith(color: color) { (color) in
            self.changeGridColor(of: grid, to: color)
            self.updateUIForGridColor()
        }
        controller.title = "Grid Color"
        show(controller, sender: self)
    }

    private func showLabelColorPicker() {
        guard let grid = mapView?.grid,
            let color = labelColor(of: grid) else {
            return
        }
        let controller = ColorPickerViewController.instantiateWith(color: color) { (color) in
            self.changeLabelColor(of: grid, to: color)
            self.updateUIForLabelColor()
        }
        controller.title = "Label Color"
        show(controller, sender: self)
    }
}

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