从子视图模型计算 属性 不会更新@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)
}
}
}
我有一个父视图和一个子视图,每个视图都有自己的视图模型。父视图注入子视图的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)
}
}
}