简体   繁体   English

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

[英]Combine onChange and onAppear events in SwiftUI view?

I'm observe a property on a view using the onChange modifier.我正在使用onChange修饰符观察视图上的属性。 However, I'd also like the same piece of code to run on the initial value as well because sometimes the data is injected in the initializer or asynchronously loaded later.但是,我也希望在初始值上运行同一段代码,因为有时数据会注入到初始化程序中或稍后异步加载。

For example, I have a view that gets a model injected.例如,我有一个注入模型的视图。 Sometimes this model has data in it to begin with (like previews), or is asynchronously retrieved from the network.有时,此模型中开始包含数据(如预览),或者从网络异步检索。

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

In both onAppear and onChange cases, I'd like to run someLogic(counter:) .onAppearonChange情况下,我都想运行someLogic(counter:) Is there a better way to get this behaviour or combine them?有没有更好的方法来获得这种行为或将它们结合起来?

It looks like onReceive may be what you need.看起来onReceive可能是您需要的。 Instead of:代替:

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

you could do:你可以这样做:

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

The difference between onChange and onReceive is that the latter also fires when the view is initialised. onChangeonReceive之间的区别在于后者也会在视图初始化时触发。


onChange改变

If you take a closer look at onChange , you'll see that it performs an action only when a value changes (and this doesn't happen when a view is initialised).如果您仔细查看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接收

However, the counter's publisher will emit the value also when a view is initialised.但是,计数器的发布者也会在视图初始化时发出该值。 This will make onReceive perform an action passed as a parameter.这将使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

Just note that onReceive is not an equivalent of onChange + onAppear .请注意, onReceive等同onChange + onAppear

onAppear is called when a view appears but in some cases a view may be initialised again without firing onAppear . onAppear在视图出现时被调用,但在某些情况下,视图可能会在不触发onAppear的情况下再次初始化。

Apple understood this requirement so they gave us task(id:priority:_:) which will run the operation both when the views described by the View data struct appear on screen and if the value supplied to id: changes (it must be an Equatable ). Apple 理解这个要求,所以他们给了我们task(id:priority:_:)当 View 数据结构描述的视图出现在屏幕上时以及如果提供给id:的值发生变化(它必须是Equatable )。 It has the added benefits that the operation is cancelled if the value is changed so the next operation doesn't overlap with the first;它的额外好处是,如果值发生更改,操作将被取消,因此下一个操作不会与第一个操作重叠; it also is cancelled if the View disappears.如果视图消失,它也会被取消。

Example例子

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

Tell you a real accident.告诉你一个真实的事故。

In a navigation stack, push A -> push B. Now B is the on the screen在导航堆栈中,推 A -> 推 B。现在 B 是屏幕上的

onReceive can be received on A too even it's not on the screen, while onChange only can be triggered on B. In this case, onReceive is very dangerous. onReceive也可以在 A 上接收,即使它不在屏幕上,而onChange只能在 B 上触发。在这种情况下, onReceive是非常危险的。

For example, if you have a global state and try to push to another view once onReceived or onChange is triggered on both A and B, then you will see the view is pushed twice since both A and B triggers.例如,如果您有一个全局状态,并尝试在 A 和 B 上触发onReceivedonChange后尝试推送到另一个视图,那么您将看到视图被推送两次,因为 A 和 B 都触发了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM