ios - subview 模型的计算属性不会更新父 View 模型的 @ObservedObject

标签 ios swift mvvm swiftui combine

我有一个父 View 和一个 subview ,每个 View 都有自己的 viewModel。父 View 注入(inject) subview 的 viewModel。

父 View 无法正确响应 subview 的计算属性 isFormInvalid 的更改( subview 可以)。

@Published 无法添加到计算属性中,并且我在该区域看到的其他问题/答案并没有像这个问题那样专注于拥有单独的 viewModel。我希望单独的 viewModel 来提高可测试性,因为 subview 可​​能会变得相当复杂的形式。

这是一个可以最小化重现该问题的文件:

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

谢谢!

最佳答案

计算属性不会触发任何更新。正是对 @Published 属性的更改触发了更新,当发生这种情况时,计算属性将被重新评估。这按预期工作,您可以在 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)
        }
    }
}

关于ios - subview 模型的计算属性不会更新父 View 模型的 @ObservedObject,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69778621/

相关文章:

ios - 在 Swift 中遍历 View Controller 层次结构

ios - 将 Swift 框架导入 Obj-C

ios - 如何为遗留 TableView 编写 SwiftUI 包装器

android - 如何发现我为 iOS 编译

ios - 使用 Swift 2 的 HJImagesToVideo

ios - UIPercentDrivenInteractiveTransition 取消问题

c# - 在父 View 模型中公开 subview 模型的属性

mvvm - 如何在 View 模型中向新的livedata builder添加参数

c# - WPF如何更新CanExecute

android - 开发服务器以托管智能手机应用程序内容