Manage operational layers

View on GitHub
Sample viewer app

Add, remove, and reorder operational layers in a map.

Image of manage operational layers 1 Image of manage operational layers 2

Use case

Operational layers display the primary content of the map and usually provide dynamic content for the user to interact with (as opposed to basemap layers that provide context).

The order of operational layers in a map determines the visual hierarchy of layers in the view. You can bring attention to a specific layer by rendering above other layers.

How to use the sample

Tap the toolbar button to display the operational layers that are currently displayed in the map. In the first section, tap "-" button to remove a layer, or tap and hold the reordering control and drag to reorder a layer. The map will be updated automatically.

The second section shows layers that have been removed from the map. Tap one to add it back to the map.

How it works

  1. Get the operational layers from the map's operationalLayers property.
  2. Add or remove layers by modifying the operationalLayers array. The last layer in the array will be rendered on top.

Relevant API

  • AGSLayer
  • AGSMap

Additional information

You cannot add the same layer to the map multiple times or add the same layer to multiple maps. Instead, clone the layer using AGSLayer.copy() to create a new instance.

Tags

add, delete, layer, map, remove

Sample Code

MMLLayersViewController.swiftManageMapLayersViewController.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
// Copyright 2016 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 MMLLayersViewController: UITableViewController {
    /// The map for which to manage the operational layers.
    weak var map: AGSMap?

    /// Every layer on the map or that could be added to the map.
    var allLayers: [AGSLayer] = []

    /// The layers present in `allLayers` but not in the map's `operationalLayers`.
    private var removedLayers: [AGSLayer] {
        if let operationalLayers = map?.operationalLayers as? [AGSLayer] {
            return allLayers.filter { !operationalLayers.contains($0) }
        }
        return []
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // enable the editing UI
        tableView.isEditing = true
    }

    /// A convenience type for the table view sections.
    private enum Section: CaseIterable {
        case operational, removed

        var label: String {
            switch self {
            case .operational:
                 return "Operational Layers"
            case .removed:
                 return "Removed Layers"
            }
        }
    }

    // MARK: - UITableViewDataSource

    override func numberOfSections(in tableView: UITableView) -> Int {
        return Section.allCases.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch Section.allCases[section] {
        case .operational:
            return map?.operationalLayers.count ?? 0
        case .removed:
            return removedLayers.count
        }
    }

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return Section.allCases[section].label
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "LayerCell", for: indexPath)

        let layerForIndexPath: AGSLayer? = {
            switch Section.allCases[indexPath.section] {
            case .operational:
                return map?.operationalLayers.object(at: indexPath.row) as? AGSLayer
            case .removed:
                return removedLayers[indexPath.row]
            }
        }()
        cell.textLabel?.text = layerForIndexPath?.name
        return cell
    }

    // MARK: - UITableViewDelegate

    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        switch Section.allCases[indexPath.section] {
        case .operational:
            return .delete
        case .removed:
            return .insert
        }
    }

    override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return Section.allCases[indexPath.section] == .operational
    }

    override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
        if Section.allCases[sourceIndexPath.section] == .operational,
            Section.allCases[proposedDestinationIndexPath.section] == .operational {
            // only allow reordering within the operational layers section
            return proposedDestinationIndexPath
        }
        return sourceIndexPath
    }

    override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        // update the order of layers in the array

        if destinationIndexPath != sourceIndexPath {
            map?.operationalLayers.exchangeObject(at: sourceIndexPath.row, withObjectAt: destinationIndexPath.row)
        }
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        switch editingStyle {
        case .delete:
            // move the layer from the operational layers to the removed layers
            guard let layerToRemove = map?.operationalLayers.object(at: indexPath.row) as? AGSLayer else {
                return
            }
            map?.operationalLayers.removeObject(at: indexPath.row)

            // update the table
            tableView.performBatchUpdates({
                // delete the row
                tableView.deleteRows(at: [indexPath], with: .automatic)

                let newIndexPath = IndexPath(row: removedLayers.firstIndex(of: layerToRemove)!, section: 1)
                // insert the new row
                tableView.insertRows(at: [newIndexPath], with: .fade)
            })
        case .insert:
            // move the layer from the removed layers to the operational layers
            let layer = removedLayers[indexPath.row]
            map?.operationalLayers.insert(layer, at: 0)

            // update the table
            tableView.performBatchUpdates({
                // delete the row
                tableView.deleteRows(at: [indexPath], with: .fade)
                let newIndexPath = IndexPath(row: 0, section: 0)
                // insert the new row
                tableView.insertRows(at: [newIndexPath], with: .fade)
            })
        case .none:
            break
        @unknown default:
            break
        }
    }
}

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