Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I'm observe a property on a view using the onChange modifier. 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:). Is there a better way to get this behaviour or combine them?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
1.3k views
Welcome To Ask or Share your Answers For Others

1 Answer

It looks like onReceive may be what you need. 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.


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

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

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

onAppear is called when a view appears but in some cases a view may be initialised again without firing onAppear.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...