Mvvm SwiftUI-@绑定到一个计算属性,该属性访问ObserveObject属性内的值,从而复制变量?

Mvvm SwiftUI-@绑定到一个计算属性,该属性访问ObserveObject属性内的值,从而复制变量?,mvvm,binding,swiftui,observableobject,Mvvm,Binding,Swiftui,Observableobject,在下面的代码(项目中某些代码的精简版本)中,我使用了一个MVVM模式,它有两个视图: ViewA-显示存储在ObserveObject ViewModel中的值 ViewB-显示相同的值,并具有更改该值的滑块,该值通过绑定传递给视图 在ViewModelA内部,我有一个computed属性,它既可以避免视图直接访问模型,也可以在模型内部的值(正在显示的值)更改时执行一些其他操作。 我还使用绑定将计算出的值传递给ViewModelB,该绑定充当ViewB的StateObject。但是,当拖动滑

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

  • ViewA-显示存储在ObserveObject ViewModel中的值
  • ViewB-显示相同的值,并具有更改该值的滑块,该值通过绑定传递给视图
在ViewModelA内部,我有一个computed属性,它既可以避免视图直接访问模型,也可以在模型内部的值(正在显示的值)更改时执行一些其他操作。

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

但是更改是如何向上传播的(我想是通过绑定的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)
        }
    }
}


实际上,它看起来像是破碎的单一来源或真相概念。取而代之的是,以下方法很有效(
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)
        }
    }
}

实际上,它看起来像是破碎的单一来源或真相概念。取而代之的是,以下方法很有效(
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)
        }
    }
}
如果你只看你不能去的地方,你可能会错过下面的财富

打破单一真相来源,以及通过绑定共享来打破
@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

例如:

另一方面,“不直接从视图访问模型”的意义是什么

当您的模型是值类型时,它没有任何意义

尤其是当你像派对上的饼干一样传递视图模型引用时,这样每个人都可以拥有它

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

打破单一真相来源,以及通过绑定共享来打破
@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

例如:

另一方面,“不直接从视图访问模型”的意义是什么

当您的模型是值类型时,它没有任何意义


尤其是当你像派对上的cookies一样传递视图模型引用时,这样每个人都可以拥有它。

你通常不会在视图模型中使用绑定。绑定用于视图的值类型属性。是的,我从未见过任何人在ViewModels中使用绑定,但如果我坚持使用mvvm,其中视图和模型之间的连接应通过ViewModel进行,因此如果我想使用整个应用程序中使用的信息更改存储在模型中的值,例如,作为一个滑块,我看到的唯一选项要么是每个模型只使用一个ViewModel,要么必须通过绑定在ViewModels之间传递模型(或其属性)(我不明白为什么不这样做)。或者我遗漏了什么?绑定是为了别的东西,所以你没有正确地使用它。最佳做法是将视图的角色减少为单个作业。如果需要在multip中更改单个模型
struct ContentView: View {
    @ObservedObject var viewModelA = ViewModelA()

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

            ViewB(value: viewModelA)
        }
    }
}