【问题标题】:Computed Property from Child's ViewModel does not update @ObservedObject Parent's ViewModelChild 的 ViewModel 的计算属性不会更新 @ObservedObject Parent 的 ViewModel
【发布时间】:2023-03-20 21:39:01
【问题描述】:

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

父视图没有正确响应子计算属性isFormInvalid 上的更改(子视图会)。

@Published 不能添加到计算属性中,我在该区域看到的其他问题/答案并没有像这个问题那样专注于拥有单独的视图模型。我想要单独的 viewModel 来增加可测试性,因为子视图可​​能会变得非常复杂。

这里有一个文件可以最小化重现问题:

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

谢谢!

【问题讨论】:

  • 不要为isFormValid 使用计算属性,使其成为@Published 属性,并在name 属性上使用didSet 来设置isFormValid
  • 但是如何处理多个字段?
  • 您需要在模型中创建一个validate 函数,并在任何属性发生变化时调用它。

标签: ios swift mvvm swiftui combine


【解决方案1】:

计算属性不会触发任何更新。触发更新的是更改为 @Publised 的属性,当发生这种情况时,计算的属性将被重新评估。这可以按预期工作,您可以在 ChildView 中看到。您面临的问题是ObservableObjects 并不是真正为链接而设计的(更新到子级不会触发对父级的更新。您可以通过重新发布子级的更新来解决这个问题:您已经订阅了子级的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)
        }
    }
}

【讨论】:

  • 这行得通,谢谢。这在多大程度上“反对/围绕框架工作”,你认为这是 SwiftUI/Combine 缺少的东西,并且会在未来添加吗?我正在尝试评估这是多么 hacky,以及我是否应该以某种方式重构以避免这种解决方案。
猜你喜欢
  • 2020-07-18
  • 2020-02-24
  • 1970-01-01
  • 2011-09-19
  • 1970-01-01
  • 1970-01-01
  • 2015-12-08
  • 2019-08-06
  • 1970-01-01
相关资源
最近更新 更多