Select to view content in your preferred language

Swift Layer List Examples

980
3
Jump to solution
04-06-2023 05:36 AM
Justin_Greco
Frequent Contributor

Are there any examples of building a layer list from a web map, particularly one with group layers?  I am get the layers from map.operationalLayers to show in a List view, but am struggling with getting the sublayer for group layers to appear as children, since the .  The layer list is not part of the toolkit, are there plans to have add one in the future?

0 Kudos
1 Solution

Accepted Solutions
Justin_Greco
Frequent Contributor

Was a bit easier than I had thought.  Here is a code sample:

import SwiftUI
import ArcGIS
struct LayerView: View {
    var map: Map
    var body: some View {
        List {
            ForEach(map.operationalLayers, id:\.self) { layer in
                SublayerView(layer: layer)
            }
        }
        .onAppear {
            //make all group layers visible by default
            map.operationalLayers.forEach { layer in
                if (layer.subLayerContents.count > 0) {
                    layer.isVisible = true
                    layer.subLayerContents.forEach { sublayer in
                        if (sublayer.subLayerContents.count > 0) {
                            sublayer.isVisible = true
                        }
                    }
                }
            }
        }
    }
}

struct SublayerView: View {
    var layer: Layer
    @State private var expanded: Bool = false
    var body: some View {
        if (layer.subLayerContents.count > 0) {
            DisclosureGroup(layer.name, isExpanded: $expanded) {
                ForEach(layer.subLayerContents.reversed(), id:\.self.name) {
                    sublayer in
                    if (sublayer.subLayerContents.count > 0) {
                        SublayerView(layer: sublayer as! Layer)
                    } else {
                        Toggle(isOn: Binding<Bool>(get: {sublayer.isVisible}, set: {sublayer.isVisible = $0
                        })) {
                            Text(sublayer.name)
                        }
                    }
                }
            }
        } else {
            Toggle(isOn: Binding<Bool>(get: {layer.isVisible}, set: {layer.isVisible = $0})) {
                Text(layer.name)
            }
        }
    }
}

View solution in original post

3 Replies
Justin_Greco
Frequent Contributor

Was a bit easier than I had thought.  Here is a code sample:

import SwiftUI
import ArcGIS
struct LayerView: View {
    var map: Map
    var body: some View {
        List {
            ForEach(map.operationalLayers, id:\.self) { layer in
                SublayerView(layer: layer)
            }
        }
        .onAppear {
            //make all group layers visible by default
            map.operationalLayers.forEach { layer in
                if (layer.subLayerContents.count > 0) {
                    layer.isVisible = true
                    layer.subLayerContents.forEach { sublayer in
                        if (sublayer.subLayerContents.count > 0) {
                            sublayer.isVisible = true
                        }
                    }
                }
            }
        }
    }
}

struct SublayerView: View {
    var layer: Layer
    @State private var expanded: Bool = false
    var body: some View {
        if (layer.subLayerContents.count > 0) {
            DisclosureGroup(layer.name, isExpanded: $expanded) {
                ForEach(layer.subLayerContents.reversed(), id:\.self.name) {
                    sublayer in
                    if (sublayer.subLayerContents.count > 0) {
                        SublayerView(layer: sublayer as! Layer)
                    } else {
                        Toggle(isOn: Binding<Bool>(get: {sublayer.isVisible}, set: {sublayer.isVisible = $0
                        })) {
                            Text(sublayer.name)
                        }
                    }
                }
            }
        } else {
            Toggle(isOn: Binding<Bool>(get: {layer.isVisible}, set: {layer.isVisible = $0})) {
                Text(layer.name)
            }
        }
    }
}
MarkDostal
Esri Regular Contributor

Hello! Thank you for your question and I'm glad you found a solution. A legend or "layers list" is something that we will be implementing in the Swift Toolkit at some point, but there's no firm release date yet.

In the meantime, you solution looks good! You can also take a look at the v100.15 Toolkit's LegendViewController, and the actual code.

There are a few things in that code that may be of interest, depending on your requirements:

- Including base maps in the layers list

- Ensuring the Map and Layers are loaded prior to displaying the list

- Displaying symbol swatches for layers

- Hiding/showing scale-dependent layers depending on map scale (applicable primarily for Legends).

Nice work! If you have any more questions, please reach out!

Mark

Justin_Greco
Frequent Contributor

Thank you Mark.  I was able to incorporate a legend and a opacity slider.  Here is some code in case anyone else is trying to do the same.

 

import SwiftUI
import ArcGIS
class LegendSwatch: ObservableObject, Identifiable , Hashable {
    static func == (lhs: LegendSwatch, rhs: LegendSwatch) -> Bool {
        return lhs.label == rhs.label && lhs.swatch == rhs.swatch
    }
    @Published var label: String
    @Published var swatch: UIImage

    init(label: String, swatch: UIImage) {
        self.label = label
        self.swatch = swatch
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(label)
        hasher.combine(swatch)
    }
}
struct LayerInfoView: View {
    @State var layer: Layer
    @State private var swatches: [LegendSwatch] = []
    var body: some View {
        ScrollView {
            VStack {
                Text("Opacity")
                Slider(value: $layer.opacity, in: 0...1, step: 0.1) {
                    
                }
               minimumValueLabel: {
                   Text("0%")
               } maximumValueLabel: {
                   Text("100%")
               }
                ForEach(swatches, id: \.self) { swatch in
                    HStack {
                        Image(uiImage: swatch.swatch)
                        Text(swatch.label)
                    }
                }
            }
        }
        .task {
            let infos: [LegendInfo] = try! await layer.legendInfos
            infos.forEach { info in
                Task {
                    let swatch = try? await info.symbol?.makeSwatch(scale: 1.0)
                    self.swatches.append(LegendSwatch(label: info.name, swatch: swatch!))
                }
            }
        }
    }
}
0 Kudos