1

鉴于我在下面概述的设置,我试图确定为什么ChildView's.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's是不合适的viewModel@ObservedObject因为ChildView拥有viewModel@ObservedObject给了我我需要的行为,而@StateObject没有。

以下是我要注意的差异:

  • 使用 时@ObservedObject,我可以点击黑色区域并查看应用于白色文本和红色矩形的更改。ParentView我还可以点击红色矩形并通过白色文本查看观察到的变化。
  • 使用 时@StateObject,我可以点击黑色区域并查看应用于白色文本和红色矩形的更改。问题在于我可以点击此处的红色矩形并查看反映的更改ParentViewChildView无法识别更改(旋转不更改并且未打印“检测到更改”)。

实际上是@ObservedObject正确的,因为ViewModel包含 a@Binding到 a @Statecreated inParentView吗?

4

2 回答 2

1

通常,我不会为问题写出如此复杂的解决方案,但是从您对另一个答案的评论看来,您需要遵守某些架构问题。

您的初始方法的一般问题是,onChange它只会在视图触发渲染时运行。通常,发生这种情况是因为某些传入的属性已更改、@State已更改或 an 上的发布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)
    }
}

于 2022-02-13T18:50:29.163 回答
0

实际上,我们在 SwiftUI 中根本不使用视图模型对象,请参阅[Data Essentials in SwiftUI WWDC 2020]。如 4:33 的视频所示,创建一个自定义结构来保存项目,例如ChildViewConfig@State在父项中将其初始化。在处理程序中设置childViewConfig.item或添加任何变异的自定义函数。如果您需要写访问权限,则将绑定$childViewConfig或传递给子视图。$childViewConfig.item如果您坚持结构和值语义,这一切都非常简单。

于 2022-02-12T11:07:00.783 回答