Mvvm 什么';让多个SwiftUI视图能够相互更新的惯用方法是什么?

Mvvm 什么';让多个SwiftUI视图能够相互更新的惯用方法是什么?,mvvm,swiftui,Mvvm,Swiftui,我是一个iOS和UI新手,为我女儿编写了一个简单的应用程序,帮助她制作柠檬水。三个滑块控制配料(水、糖、柠檬),两个滑块控制口味参数(糖醋)。其想法是,对于给定的甜度和酸度,调整任何成分滑块将更新其他滑块,以满足指定的甜度和酸度。您可以看到以下效果: 正如你所看到的,我已经让它工作了,但这不是一个很好的解决方案;视图中的一切都是以一种非常迫切的方式发生的,滑块值的更改驱动了对updateIngredients()的调用 我希望有一个视图模型句柄来确定要显示的值,一个模型在每次滑块值更改时处理成

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

正如你所看到的,我已经让它工作了,但这不是一个很好的解决方案;视图中的一切都是以一种非常迫切的方式发生的,滑块值的更改驱动了对
updateIngredients()
的调用

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

我已使视图模型
可观察
并发布了其属性,并将这些属性绑定到滑块值。这很好。为了简单起见,我将跳过模型并将成分重新计算放在视图模型中:

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
            ...
        }
    }

...

}
struct ContentView:View{
@观察对象变量显示:DisplayCalculator
...
VStack{
文本(“柠檬:\(display.numLemons,说明符:%.0f”))
滑块(值:$display.numLemons,in:display.lemons[“minValue”]!…display.lemons[“maxValue”]!,step:display.lemons[“step”!)
...
}
//视图模型
类DisplayCalculator:ObservableObject{
//初始配方为200g糖、1400ml水、7个柠檬
@已发布变量numLemons:Double=7
@发布重量:双倍=200
@公布重量:双=1400
@公布变量lemonWaterRatio:Double=7/1400
@公布的var糖水比:双=200/1400
lemons:Dictionary=[“minValue”:0,“maxValue”:40,“step”:1]
...
//我怎么称呼这个?
func更新元素(uu成分:字符串){
开关(成分){
案例“柠檬”:
//根据self.lemonWaterRatio和self.sugarWaterRatio设置self.sugarWeight和self.waterWeight
...
}
}
...
}
我遇到的问题是,每次其中一个值发生更改时,我都需要重新计算整个值集。我知道触发重新计算的唯一方法是从发布的属性上的
willSet
驱动它。但是所有属性都需要
willSet
,因此当重新计算更新所有其他值时,那些珀蒂观察者无限触发


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

这里是一种可能的方法

  • 首先在滑块上使用一个
    .signature(TapSignature().onEnded())
    。这个函数调用函数,然后更新您的值

  • 确保在函数调用中进行区分以更新您的值。否则,正如您已经指出的,您将陷入无穷循环。 我是使用switch语句来实现的

  • 以下是运行代码的示例:

    注意:只有Lemon slider进行数学计算。添加其他计算。我还制作了成分和比率枚举。这有助于我有一个更清晰的代码,并且更容易区分案例

    在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
    }
    
    
    struct ContentView:View{
    @ObservedObject变量显示:DisplayCalculator=DisplayCalculator()
    var body:一些观点{
    VStack(){
    文本(“柠檬:\(display.numLemons,说明符:%.0f”))
    滑块(值:$display.numLemons,in:display.lemons[“minValue”]!…display.lemons[“maxValue”]!,step:display.lemons[“step”!)。手势(点击手势().oneded({uu)in
    self.display.updateValue(配料:配料.numLemons)
    }))
    文本(“糖:\(display.sugarWeight,说明符:%.0f”))
    滑块(值:$display.sugarWeight,in:0…2000,步数:10)。手势(Tap手势().oneded({uuin
    self.display.updateValue(配料:配料.糖重)
    }))
    文本(“水:\(display.waterWeight,说明符:%.0f”))
    滑块(值:$display.waterWeight,in:0…5000,步长:50)
    self.display.updateValue(配料:配料.水重)
    }))
    }.padding()
    }
    }
    类DisplayCalculator:ObservableObject{
    @已发布的变量numLemons:Double=components.numLemons.rawValue
    @发布的var sugarWeight:Double=配料.sugarWeight.rawValue
    @已发布变量waterWeight:Double=配料.waterWeight.rawValue
    lemons:Dictionary=[“minValue”:0,“maxValue”:40,“step”:1]
    func更新值(成分:成分){
    开关元件{
    案例.努姆莱蒙斯:
    self.waterWeight=self.numLemons/IngredientsMultiplier.lemonWaterRatio.rawValue
    self.sugarWeight=self.waterWeight*IngredientsMultiplier.sugarWaterRatio.rawValue
    例.糖重:
    //算算
    打印(“计算”)
    案例.水重:
    //算算
    打印(“计算”)
    }
    }
    }
    枚举成分:双份{
    //初始配方为200g糖、1400ml水、7个柠檬
    案例numLemons=7
    箱重=200
    箱水重量=1400
    }
    枚举IngCreditsMultiplier:双精度{
    案例柠檬水比率=0.005//7/1400
    案例糖水比=0.1428//200/1400
    }
    
    看起来像这样:

    我希望这有帮助

    但是所有属性都需要设置willSet,因此当重新计算更新所有其他值时,这些属性将被触发
    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
    }
    
    
    var lemons = 0 { willSet { if newValue != lemons {updateIngredients("lemon")}}}
    var sugar = 0 { willSet { if newValue != sugar {updateIngredients("sugar")}}}