Fleet Phil
Fleet Phil

Reputation: 461

Disable a segment in a SwiftUI SegmentedPickerStyle Picker?

My SwiftUI app has a segmented Picker and I want to be able to disable one or more options depending on availability of options retrieved from a network call. The View code looks something like:

    @State private var profileMetricSelection: Int = 0
    private var profileMetrics: [RVStreamMetric] = [.speed, .heartRate, .cadence, .power, .altitude]
    @State private var metricDisabled = [true, true, true, true, true]

    var body: some View {
        VStack(alignment: .leading, spacing: 2.0) {
            ...(some views)...
            Picker(selection: $profileMetricSelection, label: Text("")) {
                ForEach(0 ..< profileMetrics.count) { index in
                    Text(self.profileMetrics[index].shortName).tag(index)
                }
            }.pickerStyle(SegmentedPickerStyle())
            ...(some more views)...
        }
    }

What I want to be able to do is modify the metricDisabled array based on network data so the view redraws enabling the relevant segments. In UIKit this can be done by calls to setEnabled(_:forSegmentAt:) on the UISegmentedControl but I can't find a way of doing this with the SwiftUI Picker

I know I can resort to wrapping a UISegmentedControl in a UIViewRepresentable but before that I just wanted to check I'm not missing something...

Upvotes: 4

Views: 3367

Answers (1)

user3441734
user3441734

Reputation: 17534

you can use this simple trick

import SwiftUI

struct ContentView: View {
    @State var selection = 0
    let data = [1, 2, 3, 4, 5]
    let disabled = [2, 3] // at index 2, 3
    var body: some View {

        let binding = Binding<Int>(get: {
            self.selection
        }) { (i) in
            if self.disabled.contains(i) {} else {
                self.selection = i
            }
        }

        return VStack {
            Picker(selection: binding, label: Text("label")) {
                ForEach(0 ..< data.count) { (i) in
                    Text("\(self.data[i])")
                }
            }.pickerStyle(SegmentedPickerStyle())
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

enter image description here

Maybe something like

ForEach(0 ..< data.count) { (i) in
    if !self.disabled.contains(i) {
        Text("\(self.data[i])")
    } else {
        Spacer()
    }
}

could help to visualize it better

enter image description here

NOTES (based on the discussion)

From user perspective, the Picker is one control, which could be in disabled / enabled state.

The option selected from Picker is not control, it is some value. If you make a list of controls presented to the user, some of them could be disabled, just to inform the user, that the action associated with it is not currently available (like menu, some buttons collection etc.)

I suggest you to show in Picker only values which could be selected. This collection of values could be updated any time.

UPDATE

Do you like something like this?

enter image description here

No problem at all ... (copy - paste - try - modify ...)

import SwiftUI

struct Data: Identifiable {
    let id: Int
    let value: Int
    var disabled: Bool
}

struct ContentView: View {
    @State var selection = -1
    @State var data = [Data(id: 0, value: 10, disabled: true), Data(id: 1, value: 20, disabled: true), Data(id: 2, value: 3, disabled: true), Data(id: 3, value: 4, disabled: true), Data(id: 4, value: 5, disabled: true)]
    var filteredData: [Data] {
        data.filter({ (item) -> Bool in
            item.disabled == false
        })
    }
    var body: some View {
        VStack {
            VStack(alignment: .leading, spacing: 0) {
                Text("Select from avaialable")
                    .padding(.horizontal)
                    .padding(.top)
                HStack {
                    GeometryReader { proxy in
                        Picker(selection: self.$selection, label: Text("label")) {
                            ForEach(self.filteredData) { (item) in
                                Text("\(item.value.description)").tag(item.id)
                            }
                        }
                        .pickerStyle(SegmentedPickerStyle())
                        .frame(width: CGFloat(self.filteredData.count) * proxy.size.width / CGFloat(self.data.count), alignment: .topLeading)

                        Spacer()
                    }.frame(height: 40)
                }.padding()
            }.background(Color.yellow.opacity(0.2)).cornerRadius(20)
            Button(action: {
                (0 ..< self.data.count).forEach { (i) in
                    self.data[i].disabled = false
                }
            }) {
                Text("Enable all")
            }
            Button(action: {
                self.data[self.selection].disabled = true
                self.selection = -1
            }) {
                Text("Disable selected")
            }.disabled(selection < 0)
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Upvotes: 6

Related Questions