1

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

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

在 ViewModelA 内部,我有一个计算属性,它既可以避免视图直接访问模型,也可以在模型(正在显示的那个)内部的值发生更改时执行一些其他操作。

我还使用 Binding 将该计算值传递给 ViewModelB,它充当 ViewB 的 StateObject。但是,当拖动 Slider 以更改该值时,ViewA 上的值会更改,但在 ViewB 上不会更改,并且滑块本身不会滑动。正如预期的那样,在调试时,Binding 中的 WrappedValue 没有改变。

但是变化是如何向上传播的(通过绑定的设置器,我想)而不是向下传播回 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)
        }
    }
}

视图模型:

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

2 回答 2

2

如果你只看你不能去的地方,你可能会错过下面的财富

打破单一事实来源,以及@StateObject通过 Binding 共享它来破坏本地​​(私有)财产是你不能去的两个地方。

@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,或者必须通过绑定在 ViewModel 之间传递模型(或其属性)

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

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

例如;

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

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

            ViewB(value: viewModelA)
        }
    }
}

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

当您的模型是值类型时,它是零意义的。

尤其是当您在聚会中传递视图模型引用时,就像 cookie 一样,每个人都可以拥有它。

于 2020-07-17T07:16:40.970 回答
0

真的,它看起来像是破碎的单一来源或真理概念。相反,以下只是工作(ViewModelB可能需要某些东西,但不适用于这种情况)

使用 Xcode 12 / iOS 14 测试

演示

仅修改部分:

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)
        }
    }
}
于 2020-07-16T14:05:15.320 回答