从子视图模型计算 属性 不会更新@ObservedObject 父视图模型

Computed Property from Child's ViewModel does not update @ObservedObject Parent's ViewModel

我有一个父视图和一个子视图,每个视图都有自己的视图模型。父视图注入子视图的viewModel。

父视图无法正确响应子视图 属性 isFormInvalid 的变化(子视图可以)。

@Published 无法添加到计算的 属性,而我在该区域看到的其他 questions/answers 并没有像这个问题那样专注于拥有单独的 viewModel。我想要单独的 viewModels 来提高可测试性,因为子视图可​​能会变得非常复杂。

这里是一个最小重现问题的文件:

import SwiftUI

extension ParentView {
    final class ViewModel: ObservableObject {
        @ObservedObject var childViewViewModel: ChildView.ViewModel

        init(childViewViewModel: ChildView.ViewModel = ChildView.ViewModel()) {
            self.childViewViewModel = childViewViewModel
        }
    }
}

struct ParentView: View {
    @ObservedObject private var viewModel: ViewModel

    init(viewModel: ViewModel = ViewModel()) {
        self.viewModel = viewModel
    }

    var body: some View {
        ChildView(viewModel: viewModel.childViewViewModel)
        .navigationBarTitle("Form", displayMode: .inline)
        .toolbar {
            ToolbarItem(placement: .confirmationAction) {
                addButton
            }
        }
    }

    private var addButton: some View {
        Button {
            print("======")
            print(viewModel.childViewViewModel.$name)
        } label: {
            Text("ParentIsValid?")
        }
        .disabled(viewModel.childViewViewModel.isFormInvalid) // FIXME: doesn't work, but the actual fields work in terms of two way updating
    }
}

struct ParentView_Previews: PreviewProvider {
    static var previews: some View {
        let childVm = ChildView.ViewModel()
        let vm = ParentView.ViewModel(childViewViewModel: childVm)

        NavigationView {
            ParentView(viewModel: vm)
        }
    }
}

// MARK: child view

extension ChildView {
    final class ViewModel: ObservableObject {

        // MARK: - public properties

        @Published var name = ""
        
        var isFormInvalid: Bool {
            print("isFormInvalid")
            return name.isEmpty
        }
    }
}

struct ChildView: View {
    @ObservedObject private var viewModel: ViewModel
    
    init(viewModel: ViewModel = ViewModel()) {
        self.viewModel = viewModel
    }
    
    var body: some View {
        Form {
            Section(header: Text("Name")) {
                nameTextField
            }
            Button {} label: {
                Text("ChildIsValid?: \(String(!viewModel.isFormInvalid))")
            }
            .disabled(viewModel.isFormInvalid)
        }
    }
    
    private var nameTextField: some View {
        TextField("Add name", text: $viewModel.name)
            .autocapitalization(.words)
    }
}

struct ChildView_Previews: PreviewProvider {
    static var previews: some View {
        let vm = ChildView.ViewModel()
        ChildView(viewModel: vm).preferredColorScheme(.light)
    }
}

谢谢!

计算属性不会触发任何更新。更改为 @Publised 属性 会触发更新,当发生这种情况时,计算的 属性 将被重新评估。这按预期工作,您可以在 ChildView 中看到。您面临的问题是 ObservableObject 并不是真正为链接而设计的(更新到子项不会触发对父项的更新。您可以通过重新发布子项的更新来解决这个问题:您已经订阅了子项的 objectWillChange 并且每次它在父级上手动触发 objectWillChange

extension ParentView {
    final class ViewModel: ObservableObject {
        @ObservedObject var childViewViewModel: ChildView.ViewModel
        private var cancellables = Set<AnyCancellable>()

        init(childViewViewModel: ChildView.ViewModel = ChildView.ViewModel()) {
            self.childViewViewModel = childViewViewModel
            childViewViewModel
                .objectWillChange
                .receive(on: RunLoop.main)
                .sink { [weak self] _ in
                    self?.objectWillChange.send()
                }
                .store(in: &cancellables)
        }
    }
}