Skip to content

Browse WMS layers

View on GitHub

Connect to a WMS service and show the available layers and sublayers.

Image of browse WMS layers sample

Use case

WMS services often contain many layers and sublayers. Presenting the layers and sublayers in a UI allows you to explore what is available in the service and add individual layers to a map.

How to use the sample

  1. Open the sample.
  2. Tap "Layer Visibility" to see a hierarchical list of layers and sublayers.
  3. Select a layer to enable it for display. If the layer has any children, the children will also be selected.

How it works

  1. A WMSService is created and loaded.
  2. WMSService has a serviceInfo property, which is a WMSServiceInfo. WMSService has a WMSLayerInfo object for each layer (excluding sublayers) in the layerInfos collection.
  3. Models are recursively created for each sublayer.
    • The model has an isVisible property which sets the visibility for the associated sublayer.
  4. Once the layer selection has been updated, a WMSLayer with the selected sublayers is created and added to the operational layers of the map.

Relevant API

  • WMSLayer
  • WMSLayerInfo
  • WMSService
  • WMSServiceInfo

About the data

This sample shows Weather Radar Base Reflectivity Mosaics produced by the US NOAA National Weather Service. The service provides weather radar data from the NWS & OAR Multi-Radar/Multi-Sensor (MRMS) System.

Tags

catalog, OGC, web map service, WMS

Sample Code

BrowseWMSLayersView.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
// Copyright 2025 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

/// A view that allows you to select visibility of WMS layers.
struct BrowseWMSLayersView: View {
    /// The map that we will display the WMS layer on.
    @State private var map = Map(basemapStyle: .arcGISDarkGray)

    /// The service we will access to display WMS data.
    @State private var wmsService = WMSService(
        url: URL(string: "https://nowcoast.noaa.gov/geoserver/observations/weather_radar/wms?SERVICE=WMS&REQUEST=GetCapabilities")!
    )

    /// The error, if any, that occurred.
    @State private var error: Error?

    /// The selected visible layers to display in the `WMSLayer`.
    @State private var selection: [WMSLayerModel] = []

    /// Models that allow us represent WMS layer infos.
    @State private var layerModels: [WMSLayerModel] = []

    /// A Boolean value indicating if the layer visibility list is showing.
    @State private var isListPresented = false

    var body: some View {
        MapView(map: map)
            .task {
                do {
                    // Load the WMS service, access the layer infos, and turn
                    // them into models.
                    try await wmsService.load()
                    let layerInfos = wmsService.serviceInfo?.layerInfos ?? []
                    layerModels = layerInfos.map(WMSLayerModel.init(layerInfo:))
                } catch {
                    self.error = error
                }
            }
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button("Layer Visibility") {
                        isListPresented.toggle()
                    }
                    .disabled(layerModels.isEmpty)
                    .popover(isPresented: $isListPresented) {
                        NavigationStack {
                            WMSLayerListView(models: layerModels, selection: $selection)
                                .navigationBarTitleDisplayMode(.inline)
                                .navigationTitle("Layer Visibility")
                                .presentationDetents([.medium])
                                .toolbar {
                                    ToolbarItem(placement: .topBarTrailing) {
                                        Button("Done") {
                                            isListPresented.toggle()
                                        }
                                    }
                                }
                        }
                        .frame(idealWidth: 320, idealHeight: 380)
                    }
                }
            }
            .onChange(of: selection) {
                map.removeAllOperationalLayers()
                guard !selection.isEmpty else { return }
                let wmsLayer = WMSLayer(layerInfos: selection.map(\.layerInfo))
                map.addOperationalLayer(wmsLayer)
            }
            .errorAlert(presentingError: $error)
    }
}

extension BrowseWMSLayersView {
    /// A view that displays the layers and sublayers in a hierarchical list.
    struct WMSLayerListView: View {
        /// The models to display in the list.
        let models: [WMSLayerModel]

        /// The selected models that represent layer infos that we will display
        /// in the `WMSLayer` on the map.
        @Binding var selection: [WMSLayerModel]

        var body: some View {
            List(models) { model in
                OutlineGroup(models, children: \.children) { model in
                    HStack {
                        Text(model.layerInfo.title)
                        Spacer()
                        if model.kind == .display {
                            Button {
                                model.isVisible.toggle()
                            } label: {
                                Image(
                                    systemName: model.isVisible || model.isParentVisible ? "eye" : "eye.slash"
                                )
                            }
                            .buttonStyle(.borderless)
                            // Disable the button if the parent is visible because
                            // the sublayer will always display in that case.
                            .disabled(model.isParentVisible)
                            .padding(.trailing)
                        }
                    }
                    .font(.subheadline)
                    .animation(.default, value: model.isVisible)
                    .onChange(of: model.isVisible) { updateSelection() }
                }
            }
        }

        /// Updates the selection for given `isVisible`.
        private func updateSelection() {
            func visibleItems(startingWith model: WMSLayerModel) -> [WMSLayerModel] {
                // If the starting one is visible, return that only because
                // child visibility doesn't matter when the parent is visible.
                guard !model.isVisible else { return [model] }
                guard let children = model.children else { return [] }
                return children.flatMap { visibleItems(startingWith: $0) }
            }

            selection = models.flatMap { visibleItems(startingWith: $0) }
        }
    }
}

extension BrowseWMSLayersView {
    /// The model for a WMS layer info.
    @Observable
    final class WMSLayerModel: Equatable, Identifiable {
        /// The layer info that this model wraps.
        let layerInfo: WMSLayerInfo

        /// Creates a model with a given WMS layer info.
        init(layerInfo: WMSLayerInfo) {
            self.layerInfo = layerInfo
            if !layerInfo.sublayerInfos.isEmpty {
                children = layerInfo.sublayerInfos.map(WMSLayerModel.init(layerInfo:))
            } else {
                children = nil
            }
        }

        /// The kind of layer info.
        var kind: Kind {
            // If the WMS layer does not have a name but has a title,
            // it is just a category for other sublayers.
            layerInfo.name.isEmpty ? .container : .display
        }

        /// The child layer models.
        let children: [WMSLayerModel]?

        /// A Boolean value indicating if the parent is visible.
        var isParentVisible = false

        /// A Boolean value indicating if the layer is visible.
        var isVisible = false {
            didSet {
                // Update the children's `isParentVisible` property.
                children?.forEach { $0.isParentVisible = isVisible }
            }
        }

        static func == (lhs: WMSLayerModel, rhs: WMSLayerModel) -> Bool {
            lhs.layerInfo === rhs.layerInfo
        }
    }
}

extension BrowseWMSLayersView.WMSLayerModel {
    /// The kind of WMS layer info.
    enum Kind {
        /// A layer info that is just a category for other sublayers.
        case container
        /// A layer info that can be displayed.
        case display
    }
}

#Preview {
    BrowseWMSLayersView()
}

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