skip to Main Content

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.

ChildView Navigation

@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.

2

Answers


  1. Try this approach using @StateObject private var observer = Observer() to let
    the View/App observe and manage the model. Note also, NavigationView is deprecated use NavigationStack
    instead.

    @main
    struct NavigationResetIssueApp: App {
        @StateObject private var observer = Observer() // <--- here
        
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(observer)  // <--- here
            }
        }
    }
    

    Have a look at this Apple link Monitoring data it gives you some good examples of how to manage data in your app.

    Login or Signup to reply.
  2. 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)")
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search