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

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
- Open the sample.
- Tap “Layer Visibility” to see a hierarchical list of layers and sublayers.
- Select a layer to enable it for display. If the layer has any children, the children will also be selected.
How it works
- A
WMSServiceis created and loaded. WMSServicehas aserviceInfoproperty, which is aWMSServiceInfo.WMSServicehas aWMSLayerInfoobject for each layer (excluding sublayers) in thelayerInfoscollection.- Models are recursively created for each sublayer.
- The model has an
isVisibleproperty which sets the visibility for the associated sublayer.
- The model has an
- Once the layer selection has been updated, a
WMSLayerwith 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
// 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 ArcGISimport 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: (any 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()}