Is there a way to call a function when a SwiftUI Picker selection changes an EnvironmentObject?

天大地大妈咪最大 提交于 2020-07-09 13:19:55

问题


I have a SwiftUI Form I'm working with, which updates values of an EnvironmentObject. I need to call a function when the value of a Picker changes, but every way I've found is either really messy, or causes other problems. What I'm looking for is a similar way in which the Slider works, but there doesn't seem to be one. Basic code without any solution is here:

class ValueHolder : ObservableObject {

@Published var sliderValue : Float = 0.5
static let segmentedControlValues : [String] = ["one", "two", "three", "four", "five"]
@Published var segmentedControlValue : Int = 3

}
struct ContentView: View {

    @EnvironmentObject var valueHolder : ValueHolder

    func sliderFunction() {
        print(self.valueHolder.sliderValue)
    }
    func segmentedControlFunction() {
        print(ValueHolder.segmentedControlValues[self.valueHolder.segmentedControlValue])
    }

    var body: some View {
        Form {
            Text("\(self.valueHolder.sliderValue)")
            Slider(value: self.$valueHolder.sliderValue, onEditingChanged: {_ in self.sliderFunction()
            })
            Text("\(ValueHolder.segmentedControlValues[self.valueHolder.segmentedControlValue])")
            Picker("", selection: self.$valueHolder.segmentedControlValue) {
                ForEach(0..<ValueHolder.segmentedControlValues.count) {
                    Text("\(ValueHolder.segmentedControlValues[$0])")
                }
            }.pickerStyle(SegmentedPickerStyle())
        }
    }
}

After reviewing this similar (but different) question here: Is there a way to call a function when a SwiftUI Picker selection changes? I have tried using onReceive() as below, but it is also called when the Slider value changes, resulting in unwanted behavior.

.onReceive([self.valueHolder].publisher.first(), perform: {_ in
                self.segmentedControlFunction()
            })

I have tried changing the parameter for onReceive to filter it by only that value. The value passed is correct, but the segmentedControlFunction still gets called when the slider moves, not just when the picker changes.

.onReceive([self.valueHolder.segmentedControlValue].publisher.first(), perform: {_ in
                self.segmentedControlFunction()
            })

How can I get the segmentedControlFunction to be called in a similar way to the sliderFunction?


回答1:


There is much simpler approach, which looks more appropriate for me.

The generic schema as follows

Picker("Label", selection: Binding(    // proxy binding
    get: { self.viewModel.value },     // get value from storage
    set: {
            self.viewModel.value = $0  // set value to storage

            self.anySideEffectFunction() // didSet function
    }) {
       // picker content
    }



回答2:


As I was proofing the original question and tinkering with my code I accidentally stumbled across what I think is probably the best solution. So, in case it will help anyone else, here it is:

struct ContentView: View {

    @EnvironmentObject var valueHolder : ValueHolder

    @State private var sliderValueDidChange : Bool = false
    func sliderFunction() {
        if self.sliderValueDidChange {
            print("Slider value: \(self.valueHolder.sliderValue)\n")
        }
        self.sliderValueDidChange.toggle()
    }

    var segmentedControlValueDidChange : Bool {
        return self._segmentedControlValue != self.valueHolder.segmentedControlValue
    }
    @State private var _segmentedControlValue : Int = 0
    func segmentedControlFunction() {
        self._segmentedControlValue = self.valueHolder.segmentedControlValue
        print("SegmentedControl value: \(ValueHolder.segmentedControlValues[self.valueHolder.segmentedControlValue])\n")
    }


    var body: some View {
        Form {
            Text("\(self.valueHolder.sliderValue)")
            Slider(value: self.$valueHolder.sliderValue, onEditingChanged: {_ in self.sliderFunction()
            })
            Text("\(ValueHolder.segmentedControlValues[self.valueHolder.segmentedControlValue])")
            Picker("", selection: self.$valueHolder.segmentedControlValue) {
                ForEach(0..<ValueHolder.segmentedControlValues.count) {
                    Text("\(ValueHolder.segmentedControlValues[$0])")
                }
            }.pickerStyle(SegmentedPickerStyle())
                .onReceive([self._segmentedControlValue].publisher.first(), perform: {_ in
                    if self.segmentedControlValueDidChange {
                        self.segmentedControlFunction()
                    }
            })
        }
    }
}

Note that the onReceive() is for the a new @State property, which pairs with a Bool evaluating if it's the same as the @EnvironmentObject counterpart. The @State value is then updated on the function call. Also note that in the solution I've changed how the sliderFunction works so that it is only called once, when the @EnvironmentObject value actually changes. It's a bit hacky, but the Slider and Picker both work in the same way when updating values and calling their respective functions.



来源:https://stackoverflow.com/questions/61517855/is-there-a-way-to-call-a-function-when-a-swiftui-picker-selection-changes-an-env

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!