简体   繁体   English

当 SwiftUI 选择器选择更改 EnvironmentObject 时,有没有办法调用 function?

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

I have a SwiftUI Form I'm working with, which updates values of an EnvironmentObject.我有一个正在使用的 SwiftUI 表单,它更新了 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.当 Picker 的值发生变化时,我需要调用 function,但我发现的每一种方式要么真的很乱,要么导致其他问题。 What I'm looking for is a similar way in which the Slider works, but there doesn't seem to be one.我正在寻找的是 Slider 工作的类似方式,但似乎没有。 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?在此处查看此类似(但不同)的问题后: 当 SwiftUI 选择器选择发生变化时,是否有办法调用 function? I have tried using onReceive() as below, but it is also called when the Slider value changes, resulting in unwanted behavior.我已经尝试使用 onReceive() 如下,但是当 Slider 值发生变化时也会调用它,从而导致不需要的行为。

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

I have tried changing the parameter for onReceive to filter it by only that value.我尝试更改 onReceive 的参数以仅按该值过滤它。 The value passed is correct, but the segmentedControlFunction still gets called when the slider moves, not just when the picker changes.传递的值是正确的,但是当 slider 移动时,segmentedControlFunction 仍然会被调用,而不仅仅是在选择器更改时。

.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?如何以与sliderFunction 类似的方式调用segmentedControlFunction?

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
    }

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.请注意,onReceive() 用于新的 @State 属性,它与 Bool 配对,评估它是否与 @EnvironmentObject 对应物相同。 The @State value is then updated on the function call.然后在 function 调用上更新 @State 值。 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.另请注意,在解决方案中,我更改了 sliderFunction 的工作方式,以便在 @EnvironmentObject 值实际更改时仅调用一次。 It's a bit hacky, but the Slider and Picker both work in the same way when updating values and calling their respective functions.这有点 hacky,但是 Slider 和 Picker 在更新值和调用它们各自的函数时都以相同的方式工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM