Two seemingly identical .onAppear() on NavigationView root view but behave very differently: one is working correctly, the other incorrectly:
import SwiftUI
struct Child: View {
var body: some View {
Text("Child View")
.navigationTitle("Child")
}
}
struct ContentView: View {
@State var timerEventChangeThis = 0
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
// Bad: doing nothing here and this is called on timer fire while away to child view!!!
// nothing but only timer firing, this is called whle in child view
func doOnAppear() {
print("๐๐๐doOnAppear๐๐๐ ๐if you navigate to Child view, this should not be called until pop back๐คฎ๐ฅบ")
}
static func staticDoOnAppear() {
print("๐๐staticDoOnAppear๐๐")
}
var body: some View {
NavigationView {
NavigationLink(">>>> Child", destination: Child())
.navigationTitle("\(timerEventChangeThis)")
// the following three .onAppear is the same, but ...
// this .onAppear works correctly: it's called at the right time when pop back
.onAppear { print("๐ณ onAppear...") }
// ๐ฅด๐ฅด this onAppear is called when timer fire, while away to child view !!
.onAppear(perform: doOnAppear)
// and static func works correctly, too:
.onAppear(perform: Self.staticDoOnAppear)
}
.onReceive(timer) { _ in timerEventChangeThis += 1 }
}
}
what makes the second .onAppear bad? (Edit: and static func is fine!): Is this due to something how Swift work? I just cannot see anything in SwiftUI that can tell the two form apart and do thing differently.
This is what originally got me in this wild chase: simply reference a ObservableObject in the .onAppear closure cause problem:
import SwiftUI
final class AppState: ObservableObject {
@Published var counter = 0 // nothing is changing this
}
@main
struct TimerTriggerOnAppearFuncIsEvenWorstApp: App {
@StateObject var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appState)
}
}
}
struct Child: View {
var body: some View {
Text("Child View")
.navigationTitle("Child")
}
}
struct ContentView: View {
@State var timerEventChangeThis = 0
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@EnvironmentObject var appState: AppState
var body: some View {
NavigationView {
NavigationLink(">>>> Child", destination: Child())
.navigationTitle("\(timerEventChangeThis)")
// ๐ฉ๐ฉ problem here:
// when navigate to child, on timer fire mutate `timerEventChangeThis`
// onAppear() is called while you are stil in Chid View due to simply `_ = appState.counter`!!!
// set empty capture list just to be sure this problem it's not due to capture
.onAppear { [] in
print("๐พ๐พ๐พonAppear๐พ๐พ๐พ ๐if you navigate to Child view, this should not be called until pop back๐คฎ๐ฅบ")
// ๐๐๐
// just have this ObservableObject here cause the problem!!
// commented this out, problem goes away
_ = appState.counter
// this also cause problem:
// if appState.counter == 100 {
// // nothing
// }
}
}
.onReceive(timer) { _ in timerEventChangeThis += 1 }
}
}
What could possibly be the reason for such odd behavior? There is no way that I can see someone can write code to do such thing!