I have the code below where I use a NavigationLink to add multiple child views on button click. However, as soon as I increase the EnvironmentObject value, all views reset, and only ChildView 1 remains.
@main
struct NavigationResetIssueApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(Observer())
}
}
}
enum Route {
case child
}
class Observer: ObservableObject {
@Published var observedValue: Int = 0
init() {
print("Observer init")
}
}
struct ContentView: View {
@EnvironmentObject var observer: Observer
@State var navigateToRoute: Route?
var body: some View {
NavigationView {
VStack {
Text("Current observer Value: \(observer.observedValue)")
.font(.headline)
.padding()
Button("Move to Child View") {
navigateToRoute = .child
}
.padding()
NavigationLink(destination: ChildView(num: 1) ,
tag: Route.child,
selection: $navigateToRoute,
label: { })
}
}
}
}
struct ChildView: View {
@EnvironmentObject var observer: Observer
@State var viewNum:Int
@State var navigateToRoute: Route?
init(num:Int) {
self.viewNum = num
print("initializing ChildView")
}
var body: some View {
VStack {
let _ = print("chilChildViewd body")
Text("observer Value: \(observer.observedValue)")
.font(.headline)
.padding()
Button("Incrtease Observer Value") {
observer.observedValue += 1
}
.padding()
Button("Move to Next Child View") {
navigateToRoute = .child
}
.padding()
NavigationLink(destination: ChildView(num: self.viewNum+1) ,
tag: Route.child,
selection: $navigateToRoute,
label: { })
}
.navigationTitle("ChildView # \(viewNum)")
}
}
In another place, I removed the use of EnvironmentObject in the ChildView controller and changed the EnvironmentObject value after 5 seconds in the content view. Again, all child views reset, and only the first child view remains. Here is the updated code:
struct ContentView: View {
@EnvironmentObject var observer: Observer
@State var navigateToRoute: Route?
var body: some View {
NavigationView {
VStack {
Text("Current observer Value: \(observer.observedValue)")
.font(.headline)
.padding()
Button("Move to Child View") {
navigateToRoute = .child
}
.padding()
NavigationLink(destination: ChildView(num: 1) ,
tag: Route.child,
selection: $navigateToRoute,
label: { })
}
}.onAppear(perform: {
DispatchQueue.main.asyncAfter(deadline: .now() + 10, execute: {
print("------- ContentView After 5 sec -------")
observer.observedValue = 50
})
})
}
}
struct ChildView: View {
@State var viewNum:Int
@State var navigateToRoute: Route?
init(num:Int) {
self.viewNum = num
print("initializing ChildView")
}
var body: some View {
VStack {
let _ = print("chilChildViewd body")
Button("Move to Next Child View") {
navigateToRoute = .child
}
.padding()
NavigationLink(destination: ChildView(num: self.viewNum+1) ,
tag: Route.child,
selection: $navigateToRoute,
label: { })
}
.navigationTitle("ChildView # \(viewNum)")
}
}
Kindly let me know how to prevent this behavior. Thank you so much for your attention and participation.
In addition to moving the Observer out to a @State
var I think you got the navigation all mixed up. Try the NavigationStack
it is far more simple.
e.g.:
struct ContentView: View {
@EnvironmentObject var observer: Observer
// here we store the navigation
@State var path: [Int] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Current observer Value: \(observer.observedValue)")
.font(.headline)
.padding()
Button("Move to Child View") {
path.append(1)
}
.navigationDestination(for: Int.self, destination: { num in
// the value appended to path will appear here as num
// pass the path on as binding, so we can manipulate it from the childview
ChildView(num: num, path: $path)
.environmentObject(observer)
})
.padding()
}
}
}
}
struct ChildView: View {
@EnvironmentObject var observer: Observer
@Binding var path: [Int]
// no state needed here
var viewNum: Int
init(num: Int, path: Binding<[Int]>) {
self.viewNum = num
self._path = path
print("initializing ChildView")
}
var body: some View {
VStack {
let _ = print("chilChildViewd body")
Text("observer Value: \(observer.observedValue)")
.font(.headline)
.padding()
Button("Incrtease Observer Value") {
observer.observedValue += 1
}
.padding()
Button("Move to Next Child View") {
// increase the num that gets passed to the next child
path.append(viewNum + 1)
}
.padding()
}
.navigationTitle("ChildView # \(viewNum)")
}
}
Edit:
You never mentioned this being below IOS 16, but if you really want, or need to use NavigationView
try the example below. Tested on Xcode 15.4 and IOS 15 as target.
struct ContentView: View {
@EnvironmentObject private var observer: Observer
var body: some View {
NavigationView{
VStack {
Text("Current observer Value: \(observer.observedValue)")
.font(.headline)
.padding()
NavigationLink {
ChildView(num: 1)
} label: {
Text("Move to Child View")
}
}
}
}
}
struct ChildView: View {
@EnvironmentObject private var observer: Observer
var viewNum: Int
init(num: Int) {
self.viewNum = num
print("initializing ChildView")
}
var body: some View {
VStack {
let _ = print("chilChildViewd body")
Text("observer Value: \(observer.observedValue)")
.font(.headline)
.padding()
Button("Incrtease Observer Value") {
observer.observedValue += 1
}
.padding()
NavigationLink {
ChildView(num: viewNum + 1)
} label: {
Text("Move to Next Child View")
}
}
.navigationTitle("ChildView # \(viewNum)")
}
}