SwiftUI-@绑定到访问ObservableObject属性内的值的计算属性是否与变量重复?

时间:2020-07-16 13:34:16

标签: mvvm binding swiftui observableobject

在下面的代码(项目中某些代码的简化版本)中,我正在使用具有两个视图的MVVM模式:

  • ViewA-显示存储在ObservableObject ViewModel中的值;
  • ViewB-显示相同的值,并具有一个可更改该值的Slider,并使用Binding将该值传递给视图。

在ViewModelA内部,我具有一个计算属性,该属性既可以避免View直接访问Model,又可以在更改模型内部的值(正在显示的值)时执行其他一些操作。

我还使用绑定将计算的值传递给ViewModelB,该绑定充当ViewB的StateObject。但是,拖动“滑块”以更改该值时,该值在ViewA上更改,但在ViewB上不变,并且滑块本身也不会滑动。正如预期的那样,在调试时,绑定内部的wrappedValue不会更改。

但是更改如何向上传播(我想是通过Binding的setter)而不是向下传播到ViewB?

我想只有在变量被复制到某个地方并且仅在一个地方更改时,这种情况才会发生,但是我似乎无法理解实际发生的位置或发生的情况。

提前谢谢!

观看次数:

import SwiftUI

struct ContentView: View {
    @StateObject var viewModelA = ViewModelA()
    
    var body: some View {
        VStack{
            ViewA(value: viewModelA.value)
            
            ViewB(value: $viewModelA.value)
        }
    }
}

struct ViewA: View {
    let value: Double
    
    var body: some View {
        Text("\(value)").padding()
    }
}

struct ViewB: View {
    @StateObject var viewModelB: ViewModelB
    
    init(value: Binding<Double>){
        _viewModelB = StateObject(wrappedValue: ViewModelB(value: value))
    }
    
    var body: some View {
        VStack{
            Text("\(viewModelB.value)")
            
            Slider(value: $viewModelB.value, in: 0...1)
        }
    }
}

ViewModels

class ViewModelA: ObservableObject {
    @Published var model = Model()
    
    var value: Double {
        get {
            model.value
        }
        set {
            model.value = newValue
            // perform other checks and operations
        }
    }
}

class ViewModelB: ObservableObject {
    @Binding var value: Double
    
    init(value: Binding<Double>){
        self._value = value
    }
}

型号:

struct Model {
    var value: Double = 0
}

2 个答案:

答案 0 :(得分:1)

如果只看不能走的地方,您可能会想念下面的财富

打破真理的单一来源,并通过绑定共享来破坏@StateObject的本地(私有)财产,这是您无法去的两个地方。

@EnvironmentObject或更广泛地说,视图之间的“共享对象”的概念是下面的内容。

这是在没有MVVM废话的情况下执行此操作的示例:

import SwiftUI

final class EnvState: ObservableObject {@Published var value: Double = 0 }

struct ContentView: View {
    @EnvironmentObject var eos: EnvState 

    var body: some View {
        VStack{
            ViewA()
    
            ViewB()
        }
    }
}

struct ViewA: View {
    @EnvironmentObject var eos: EnvState 

    var body: some View {
        Text("\(eos.value)").padding()
    }
}

struct ViewB: View {
    @EnvironmentObject var eos: EnvState 

    var body: some View {
        VStack{
            Text("\(eos.value)")
        
            Slider(value: $eos.value, in: 0...1)
        }
    }
}

这难道不是更容易阅读,更简洁,更不易出错,是否有更少的开销以及没有严重违反基本编码原理的情况吗?

MVVM不考虑值类型。 Swift引入值类型的原因是,您不必传递共享的可变引用,也不会创建各种错误。

然而,MVVM开发人员要做的第一件事是为每个视图引入共享的可变引用,并通过绑定传递引用...

现在您的问题:

我看到的唯一选择要么是每个模型仅使用一个ViewModel,要么必须通过绑定在ViewModels之间传递模型(或其属性)

另一种选择是删除MVVM,摆脱所有视图模型,而改用@EnvironmentObject

或者,如果您不想删除MVVM,请传递@ObservedObject(您的视图模型是参考类型)而不是@Binding

例如;

struct ContentView: View {
    @ObservedObject var viewModelA = ViewModelA()

    var body: some View {
        VStack{
            ViewA(value: viewModelA)

            ViewB(value: viewModelA)
        }
    }
}

在旁注中,“不要直接从视图访问模型”有什么意义?

当您的模型为值类型时,它具有零意义。

尤其是当您在聚会中像Cookie一样传递视图模型引用时,所有人都可以使用它。

答案 1 :(得分:0)

确实,它看起来像是破碎的单源或真理概念。取而代之的是,以下方法才有效(某事可能需要ViewModelB,但在这种情况下可能不需要)

通过Xcode 12 / iOS 14测试

demo

仅修改的部分:

struct ContentView: View {
    @StateObject var viewModelA = ViewModelA()

    var body: some View {
        VStack{
            ViewA(value: viewModelA.value)

            ViewB(value: $viewModelA.model.value)
        }
    }
}

struct ViewB: View {
    @Binding var value: Double

    var body: some View {
        VStack{
            Text("\(value)")

            Slider(value: $value, in: 0...1)
        }
    }
}