在SwiftUI视图中结合onChange和onAppear事件?

11

我正在使用 onChange 修饰符观察视图上的一个属性。然而,我还希望同一段代码在初始值上运行,因为有时数据是在初始化器中注入或后来异步加载的。

例如,我有一个视图会注入一个模型。有时这个模型已经包含了一些数据(比如预览),或者是从网络异步获取的。

class MyModel: ObservableObject {
    @Published var counter = 0
}

struct ContentView: View {
    @ObservedObject var model: MyModel
    
    var body: some View {
        VStack {
            Text("Counter: \(model.counter)")
            Button("Increment") { model.counter += 1 }
        }
        .onChange(of: model.counter, perform: someLogic)
        .onAppear { someLogic(counter: model.counter) }
    }
    
    private func someLogic(counter: Int) {
        print("onAppear: \(counter)")
    }
}

onAppearonChange的情况下,我想运行someLogic(counter:)。有没有更好的方法来实现这个功能或将它们合并?


1
我不确定我完全理解了你的用例,但我认为你可以使用.onReceive(model.$counter, perform: someLogic) - New Dev
3个回答

14

看起来你可能需要使用onReceive。不要使用:

.onChange(of: model.counter, perform: someLogic)
.onAppear { someLogic(counter: model.counter) }

你可以这样做:

.onReceive(model.$counter, perform: someLogic)

onChangeonReceive的区别在于后者还会在视图初始化时触发。


onChange

如果您仔细查看onChange,您会发现它仅在值更改时执行操作(而这不会在视图初始化时发生)。

/// Adds a modifier for this view that fires an action when a specific
/// value changes.
/// ...
@inlinable public func onChange<V>(of value: V, perform action: @escaping (V) -> Void) -> some View where V : Equatable

onReceive

然而,计数器的发布者也会在视图初始化时发出值。这将使得onReceive执行作为参数传递的操作。

/// Adds an action to perform when this view detects data emitted by the
/// given publisher.
/// ...
@inlinable public func onReceive<P>(_ publisher: P, perform action: @escaping (P.Output) -> Void) -> some View where P : Publisher, P.Failure == Never

请注意,onReceive并不等同于onChange+onAppear

onAppear在视图出现时调用,但在某些情况下,一个视图可能会重新初始化而没有触发onAppear


谢谢,这正是我所需要的。而且行为比onAppear更好! - TruMan1

3

苹果公司理解这个要求,因此他们给我们提供了task(id:priority:_:)。该方法会在View数据结构描述的视图出现在屏幕上时运行操作,并在id:参数所提供的值(必须是Equatable)发生更改时运行操作。它还有额外的好处,即如果值被更改,则取消操作,以便下一个操作不会与第一个重叠;当视图消失时,也会取消操作。

示例

...
let number: Int
...
.task(id: number){
    print("task") // called both on appear and when changed. If an await method is called, it is cancelled if the number changes or if the View disappears.
    someState = await fetchSomething(number)
}
.onAppear {
    print("onAppear") // only called during appear.
}
.onChange(of: number) { number in 
    print("onChange") // only called when number is changed.
}

0

告诉你一个真实的事故。

在导航堆栈中,先推 A -> 再推 B。现在 B 在屏幕上。

onReceive 甚至可以在 A 上接收到,即使它不在屏幕上,而 onChange 只能在 B 上触发。在这种情况下,onReceive 是非常危险的。

例如,如果你有一个全局状态,并且尝试在 A 和 B 上触发 onReceivedonChange 后推到另一个视图,则会看到该视图被推了两次,因为 A 和 B 都触发了。


你需要添加一个参数来标记A/B。 - BollMose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接