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

时间:2020-10-16 15:23:08

标签: ios swift swiftui combine

我正在使用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:)。有没有更好的方法来获得这种行为或将它们结合起来?

2 个答案:

答案 0 :(得分:1)

您可能需要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,但是在某些情况下,视图可能会再次初始化而无需触发formulas()

答案 1 :(得分:0)

因为你说你是异步加载,所以我想我会分享 Apple 使用的模式,你可以在 Scrumdinger 应用教程 here 中找到它。通过使用此模式 onChange 被调用以获取初始值和任何未来值。这是其工作原理的简化版本:

import SwiftUI

@main
struct TestApp: App {
    @StateObject var myModel = MyModel()
    
    var body: some Scene {
        WindowGroup {
            ContentView(counter:$myModel.counter)
                .equatable()
                .onAppear {
                    myModel.load()
                }
        }
    }
}


class MyModel: ObservableObject {
    @Published var counter: Int?
    
    func load(){
        // Simulate asynchronously loading data
        DispatchQueue.main.asyncAfter(deadline:.now() + 3) {
            self.counter = 0
        }
    }
}

struct ContentView: View, Equatable {
    @Binding var counter: Int?
    
    var body: some View {
        VStack {
            if let counter = counter {
                Text("Counter: \(counter)")
                Button("Increment") { self.counter = counter + 1 }
                Button("Set to same number") { self.counter = counter } // this demonstrates the use of equatable to prevent a needless body recompute which is necessary when binding to a property of an observable object.
            }
            else{
                Text("Loading counter...")
            }
        }
        .onChange(of: counter, perform: someLogic)
    }
    
    private func someLogic(counter: Int?) {
        print("someLogic: \(counter ?? 0)")
    }

    static func == (lhs: ContentView, rhs: ContentView) -> Bool {
        return lhs.counter == rhs.counter
    }
}

注意:Scrumdinger 应用通过将模型的数据默认为空数组来避免可选处理。