Currently in my project, I have a navigation stack where at the parent level NavigationView, there's Timer that kicks off a withAnimation block that animates a property that buttons within the navigation stack share.
After the update to iOS 14.5, when a NavigationLink is hit in the middle of a withAnimation block (which is always in this case), the EnvironmentObjects that are passed down the navigation stack become nil.
Error thrown: Fatal error: No ObservableObject of type BackgroundViewModel found. A View.environmentObject(_:) for BackgroundViewModel may be missing as an ancestor of this view.
Definitely think it is a combination of the new way NavigationView handles animations and the way NavigationLinks handle state. Per iOS 14.5 developer release notes: NavigationView push and pop now correctly respects disabled animations. (70062477)
& The destination of NavigationLink that only differs by local state now resets that state when switching between links as expected. (72117345)
Below is a sample that will recreate the error. Any help would be greatly appreciated.
import SwiftUI
import Combine
struct ContentView: View {
@StateObject var backgroundVM = BackgroundViewModel()
@State var presentNext: Bool = false
var body: some View {
NavigationView {
VStack{
Text("Hello, world!")
.padding()
NavigationLink(
destination: View2(),
isActive: $presentNext) {
Button {
presentNext = true
} label: {
Text("Navigate")
}
}
}
.frame(width: 250, height: 250, alignment: .center)
.background(backgroundVM.backgroundColor)
}
.environmentObject(backgroundVM)
.onReceive(backgroundVM.timer) { _ in
withAnimation(.easeInOut(duration: 2.0)) {
backgroundVM.setColor()
}
}
}
}
struct View2: View {
@EnvironmentObject var backgroundVM: BackgroundViewModel
@State var presentNext: Bool = false
var body: some View {
VStack {
Text("View 2")
NavigationLink(
destination: View3(),
isActive: $presentNext) {
Button {
presentNext = true
} label: {
Text("Navigate")
}
}
}
.frame(width: 250, height: 250, alignment: .center)
.background(backgroundVM.backgroundColor)
}
}
struct View3: View {
@EnvironmentObject var backgroundVM: BackgroundViewModel
var body: some View {
VStack {
Text("View 3")
}
.frame(width: 250, height: 250, alignment: .center)
.background(backgroundVM.backgroundColor)
}
}
class BackgroundViewModel: ObservableObject {
@Published var backgroundColor: Color = .orange
@Published var colorIndex: Int = 0 {
willSet {
backgroundColor = colors[newValue]
}
}
var timer = Timer.publish(every: 1.5, on: .main, in: .common)
.autoconnect()
var colors: [Color] = [.orange, .green, .purple]
func setColor() {
if colorIndex + 1 == colors.count {
colorIndex = 0
} else {
colorIndex += 1
}
}
}