问题
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