鉴于我在下面概述的设置,我正在尝试确定为什么
ChildView
的 .onChange(of: _)
没有收到更新。
import SwiftUI
struct SomeItem: Equatable {
var doubleValue: Double
}
struct ParentView: View {
@State
private var someItem = SomeItem(doubleValue: 45)
var body: some View {
Color.black
.overlay(alignment: .top) {
Text(someItem.doubleValue.description)
.font(.system(size: 50))
.foregroundColor(.white)
}
.onTapGesture { someItem.doubleValue += 10.0 }
.overlay { ChildView(someItem: $someItem) }
}
}
struct ChildView: View {
@StateObject
var viewModel: ViewModel
init(someItem: Binding<SomeItem>) {
_viewModel = StateObject(wrappedValue: ViewModel(someItem: someItem))
}
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: 50, height: 70, alignment: .center)
.rotationEffect(
Angle(degrees: viewModel.someItem.doubleValue)
)
.onTapGesture { viewModel.changeItem() }
.onChange(of: viewModel.someItem) { _ in
print("Change Detected", viewModel.someItem.doubleValue)
}
}
}
@MainActor
final class ViewModel: ObservableObject {
@Binding
var someItem: SomeItem
public init(someItem: Binding<SomeItem>) {
self._someItem = someItem
}
public func changeItem() {
self.someItem = SomeItem(doubleValue: .zero)
}
}
有趣的是,如果我在
ChildView
中进行以下更改,我会得到我想要的行为。
将
@StateObject
更改为 @ObservedObject
将
_viewModel = StateObject(wrappedValue: ViewModel(someItem: someItem))
更改为 viewModel = ViewModel(someItem: someItem)
据我了解,
ChildView
的viewModel
成为@ObservedObject
是不合适的,因为ChildView
拥有viewModel
,但@ObservedObject
给了我我需要的行为,而@StateObject
则没有。
以下是我关注的差异:
@ObservedObject
时,我可以点击黑色区域并查看应用于白色文本和红色矩形的更改。我还可以点击红色矩形,通过白色文本查看ParentView
中观察到的变化。@StateObject
时,我可以点击黑色区域并查看应用于白色文本和红色矩形的更改。问题在于,我可以点击此处的红色矩形并查看 ParentView
中反映的更改,但 ChildView
无法识别该更改(旋转不会更改,并且不会打印“检测到更改”)。由于
@ObservedObject
包含一个ViewModel
到在@Binding
中创建的@State
,所以ParentView
实际上是正确的吗?
通常,我不会为问题编写如此复杂的解决方案,但从您对另一个答案的评论来看,您需要遵守某些架构问题。
您的初始方法的一般问题是
onChange
仅在视图触发渲染时才会运行。一般来说,发生这种情况是因为某些传入属性已更改、@State
已更改,或者 ObservableObject
上的发布者已更改。在这种情况下,这些都不是真的——你的 Binding
上有一个 ObservableObject
,但没有任何东西触发视图重新渲染。如果 Bindings
提供了发布者,那么很容易挂钩该值,但由于它们没有,似乎逻辑方法是将状态存储在父视图中,以便我们可以观看 @Published
价值。
再次强调,这不一定是我会采取的路线,但希望它符合您的要求:
struct SomeItem: Equatable {
var doubleValue: Double
}
class Store : ObservableObject {
@Published var someItem = SomeItem(doubleValue: 45)
}
struct ParentView: View {
@StateObject private var store = Store()
var body: some View {
Color.black
.overlay(alignment: .top) {
Text(store.someItem.doubleValue.description)
.font(.system(size: 50))
.foregroundColor(.white)
}
.onTapGesture { store.someItem.doubleValue += 10.0 }
.overlay { ChildView(store: store) }
}
}
struct ChildView: View {
@StateObject private var viewModel: ViewModel
init(store: Store) {
_viewModel = StateObject(wrappedValue: ViewModel(store: store))
}
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: 50, height: 70, alignment: .center)
.rotationEffect(
Angle(degrees: viewModel.store.someItem.doubleValue)
)
.onTapGesture { viewModel.changeItem() }
.onChange(of: viewModel.store.someItem.doubleValue) { _ in
print("Change Detected", viewModel.store.someItem.doubleValue)
}
}
}
@MainActor
final class ViewModel: ObservableObject {
var store: Store
var cancellable : AnyCancellable?
public init(store: Store) {
self.store = store
cancellable = store.$someItem.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
public func changeItem() {
store.someItem = SomeItem(doubleValue: .zero)
}
}
实际上,我们在 SwiftUI 中根本不使用视图模型对象,因为
View
结构层次结构就是视图模型,请参阅 [SwiftUI WWDC 2020 中的数据要点]。如视频 4:33 所示,创建一个自定义结构来保存该项目,例如ChildViewConfig
并在父级的 @State
中初始化它。在处理程序中设置 childViewConfig.item
或添加任何可变的自定义函数。如果需要写访问权限,请将绑定 $childViewConfig
或 $childViewConfig.item
传递给子视图。如果您坚持结构和值语义,这一切都会非常简单。