SwiftUI 将 Binding by ref 传递给子 ViewModel

YWs*_*ond 5 swiftui

在 SwiftUI 中,我试图在父 ViewModel 和子 ViewModel 之间创建一些绑定,这是我的场景的简化示例:

父组件:

class ParentViewModel : ObservableObject {
    @Published var name = "John Doe"

    func updateName() {
        self.name = "Jonnie Deer"
    }
}

struct ParentView: View {
    @StateObject var viewModel = ParentViewModel()

    var body: some View {
        VStack {
            Text(viewModel.name)
            ChildView(name: $viewModel.name)
            // tapping the button the text on parent view is updated but not in child view
            Button("Update", action: viewModel.updateName)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

子组件:

class ChildViewModel : ObservableObject {
    var name: Binding<String>
    
    var displayName: String {
        get {
            return "Hello " + name.wrappedValue
        }
    }
    
    init(name: Binding<String>) {
        self.name = name
    }
}

struct ChildView: View {
    @StateObject var viewModel: ChildViewModel

    var body: some View {
        Text(viewModel.displayName)
    }
    
    init(name: Binding<String>) {
        _viewModel = StateObject(wrappedValue: ChildViewModel(name: name))
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,正如评论中所述,当我点击父组件上的按钮时,ChildView 中的名称不会更新,就好像绑定丢失一样......还有其他方法可以使用更新后的值更新视图模型吗?像getDerivedStateFromProps在 React 中那样说(因为点击按钮时,ChildView::init会使用新名称调用该方法。

谢谢。

Yrb*_*Yrb 3

Apple 非常重视单一事实来源 (SSoT) 的概念,牢记这一点可以防止您陷入这样的代码困境。您遇到的问题是,当您使用 aBinding实例化子视图时,您正在转身并将其用作@StateObject. @StateObject当您这样做时,您将断开应位于 SSoT 层次结构顶部的连接。它指定您的 SSoT。否则,您有两个 SSoT,因此只能更新一个。中的视图模型ChildView应该是一个@ObservedObject,以便它连接到层次结构。另外,您可以ChildViewModel在调用时直接实例化ChildView. 初始化器只是用来解耦事物。您的观点将如下所示:

struct ParentView: View {
    @StateObject var viewModel = ParentViewModel()

    var body: some View {
        VStack {
            Text(viewModel.name)
            // You can directly use the ChildViewModel to instantiate the ChildView
            ChildView(viewModel: ChildViewModel(name: $viewModel.name))
            Button("Update", action: viewModel.updateName)
        }
    }
}

struct ChildView: View {
    // Make this an @ObservedObject not a @StateObject
    @ObservedObject var viewModel: ChildViewModel

    var body: some View {
        Text(viewModel.displayName)
    }
}
Run Code Online (Sandbox Code Playgroud)

两个视图模型都没有改变。