我正在使用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)")
}
}
在onAppear
和onChange
情况下,我都想运行someLogic(counter:)
。有没有更好的方法来获得这种行为或将它们结合起来?
答案 0 :(得分:1)
您可能需要onReceive
。代替:
.onChange(of: model.counter, perform: someLogic)
.onAppear { someLogic(counter: model.counter) }
您可以这样做:
.onReceive(model.$counter, perform: someLogic)
onChange
和onReceive
之间的区别在于,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 应用通过将模型的数据默认为空数组来避免可选处理。