【问题标题】:What's the idiomatic way to have multiple SwiftUI views able to update each other?让多个 SwiftUI 视图能够相互更新的惯用方式是什么?
【发布时间】:2020-07-05 21:24:32
【问题描述】:

我是 iOS 和 UI 新手,我正在为我的女儿编写一个简单的应用程序来帮助她制作柠檬水。三个滑块控制成分(水、糖、柠檬)和两个控制口味参数(酸甜)。这个想法是,对于给定的甜度和酸度,调整任何成分滑块将更新其他滑块以满足指定的甜度和酸度。可以看到如下效果:

如您所见,我已经成功了,但这不是一个很好的解决方案;视图中的一切都以一种非常紧迫的方式发生,滑块值的变化推动了对updateIngredients()的调用。

struct ContentView: View {
    // Initial state recipe is 200g sugar, 1400ml water, 7 lemons
    @State var numLemons: Double = 7
    @State var sugarWeight: Double = 200
    @State var waterWeight: Double = 1400
    
    @State var lemonWaterRatio: Double = 7/1400
    @State var sugarWaterRatio: Double = 200/1400

...

    // Example slider
    Text("Lemons: \(numLemons, specifier: "%.0f")")
    Slider(value: Binding(get: {
        self.numLemons
    }, set: { (newVal) in
        self.numLemons = newVal
        self.updateIngredients("lemons")
    }), in: 0...40, step: 1)

我希望有一个视图模型句柄来确定要显示的值,并且模型处理每次任何滑块值更改时重新计算成分,但我无法弄清楚如何在不陷入无限循环的情况下重新计算所有成分。

我已经制作了视图模型Observable 并发布了它的属性,并将这些属性绑定到滑块值。这可以。为了简单起见,我将跳过模型并将成分重新计算放在视图模型中:

struct ContentView: View {
    @ObservedObject var display: DisplayCalculator

...

    VStack {
        
        Text("Lemons: \(display.numLemons, specifier: "%.0f")")
        Slider(value: $display.numLemons, in: display.lemons["minValue"]!...display.lemons["maxValue"]!, step: display.lemons["step"]!)

...

}

// View Model
class DisplayCalculator: ObservableObject {
    // Initial state recipe is 200g sugar, 1400ml water, 7 lemons
    @Published var numLemons: Double = 7
    @Published var sugarWeight: Double = 200
    @Published var waterWeight: Double = 1400
    @Published var lemonWaterRatio: Double = 7/1400
    @Published var sugarWaterRatio: Double = 200/1400
    
    let lemons: Dictionary<String, Double> = [  "minValue": 0, "maxValue": 40, "step": 1]

...
    // How can I call this?
    func updateIngredients(_ ingredient: String) {
        switch(ingredient) {
        case "lemons":
            // Set self.sugarWeight and self.waterWeight based on self.lemonWaterRatio and self.sugarWaterRatio
            ...
        }
    }

...

}

我遇到的问题是,每次其中一个值发生变化时,我都需要重新计算整组值。我知道触发重新计算的唯一方法是从已发布属性上的willSet 驱动它。但是所有属性都需要willSet,因此当重新计算更新所有其他值时,那些属性观察器会无限触发。

是否有可能让任何滑块能够更新所有其他滑块,而无需从滑块本身驱动计算?

【问题讨论】:

    标签: mvvm swiftui


    【解决方案1】:

    这是一种可能的方法。

    1. 首先在滑块上使用.gesture(TapGesture().onEnded())。这个调用函数来更新你的值。

    2. 确保在函数调用中进行区分以更新您的值。否则,正如您已经指出的那样,您将陷入无限循环。 我是使用 switch 语句这样做的。

    这是一个运行代码的示例:

    注意:只有 Lemon 滑块可以执行 Math atm。添加其他的计算。我还制作了成分和比例枚举。这帮助我获得了更简洁的代码,并且更容易区分案例。

    在 iOS 13.5 上测试并运行

    struct ContentView: View {
        
        @ObservedObject var display: DisplayCalculator = DisplayCalculator()
        
        var body: some View {
            VStack() {
                Text("Lemons: \(display.numLemons, specifier: "%.0f")")
                Slider(value: $display.numLemons, in: display.lemons["minValue"]!...display.lemons["maxValue"]!, step: display.lemons["step"]!).gesture(TapGesture().onEnded({_ in
                    self.display.updateValues(ingredient: Ingredients.numLemons)
                }))
                
                Text("Sugar: \(display.sugarWeight, specifier: "%.0f")")
                Slider(value: $display.sugarWeight, in: 0...2000, step: 10).gesture(TapGesture().onEnded({_ in
                    self.display.updateValues(ingredient: Ingredients.sugarWeight)
                }))
                
                Text("Water: \(display.waterWeight, specifier: "%.0f")")
                Slider(value: $display.waterWeight, in: 0...5000, step: 50).gesture(TapGesture().onEnded({_ in
                    self.display.updateValues(ingredient: Ingredients.waterWeight)
                }))
                
            }.padding()
        }
    }
    
    class DisplayCalculator: ObservableObject {
        @Published var numLemons: Double = Ingredients.numLemons.rawValue
        
        @Published var sugarWeight: Double = Ingredients.sugarWeight.rawValue
        
        @Published var waterWeight: Double = Ingredients.waterWeight.rawValue
        
        let lemons: Dictionary<String, Double> = [  "minValue": 0, "maxValue": 40, "step": 1]
    
        func updateValues(ingredient: Ingredients) {
            
            switch ingredient {
                case .numLemons  :
                    self.waterWeight = self.numLemons/IngredientsMultiplier.lemonWaterRatio.rawValue
                    self.sugarWeight = self.waterWeight*IngredientsMultiplier.sugarWaterRatio.rawValue
                case .sugarWeight:
                    //do the math
                    print("do the math")
                case .waterWeight:
                    //do the math
                    print("do the math")
            }
        }
        
    }
    
    enum Ingredients: Double {
        
        // Initial state recipe is 200g sugar, 1400ml water, 7 lemons
        case numLemons = 7
        case sugarWeight = 200
        case waterWeight = 1400
    }
    
    enum IngredientsMultiplier: Double {
        case lemonWaterRatio = 0.005 //7/1400
        case sugarWaterRatio = 0.1428 //200/1400
    }
    
    

    看起来像这样:

    我希望这会有所帮助。

    【讨论】:

    • 这当然有助于实现一个结构更好的应用程序来帮助我女儿制作柠檬水的目标。我真的很希望能得到其他滑块的实时更新,尽管给她非常强烈的视觉反馈,显示比例如何影响成分。尽管如此,我很欣赏指向.gesture()的指针
    • 我做不到。您可以对其进行修改以立即更新值,但这会导致手势识别器终止识别。这使得无法“保持”滑动,因为您必须再次按下滑块。然后它更像是一个“步进”动作幻灯片。
    【解决方案2】:

    但所有属性都需要 willSet ,因此当重新计算更新所有其他值时,那些属性观察器会无限触发。

    您可以添加条件检查以停止触发。

    var lemons = 0 { willSet { if newValue != lemons {updateIngredients("lemon")}}}
    var sugar = 0 { willSet { if newValue != sugar {updateIngredients("sugar")}}}
    

    惯用的方法是像您一样创建自定义绑定,除了可能对 getter/setter 进行细微更改。此视图模型在功能上等同于 @State,两者都绑定到滑块。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-03
      • 2015-11-17
      • 1970-01-01
      • 1970-01-01
      • 2022-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多