iosswiftswiftuiios-navigationviewobservableobject

ObservedObject not working on NavigationLink's destination if there are updates on parent


I have two screens, a master and a detail, detail has an ObservedObject that has it's state. I also want to hide the navigation bar on master and show it on detail. To do that, I have the navigation bar hidden status as a @State property on master view and send it back to the detail view as a Binding variable.

The problem I'm having is that whenever I update that variable inside the detail screen, the ObservedObject stops working.

Here's a sample code that reproduces the issue:

struct ContentView: View {
    @State var navigationBarHidden = true

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView(navigationBarHidden: $navigationBarHidden)) {
                    Text("Go Forward")
                }
            }
            .navigationBarTitle("", displayMode: .inline)
            .navigationBarHidden(navigationBarHidden)
            .onAppear { self.navigationBarHidden = true }
        }
    }
}

class DetailViewModel: ObservableObject {
    @Published var text = "Didn't work"
}

struct DetailView: View {
    @Binding var navigationBarHidden: Bool
    @ObservedObject var viewModel = DetailViewModel()

    var body: some View {
        VStack {
            Text(viewModel.text)
        }.onAppear {
            self.navigationBarHidden = false
            self.viewModel.text = "Worked"
        }
    }
}

If I leave it as is, the text will not update to "Worked". If I remove the line self.navigationBarHidden = false, the ObservedObject will work properly and the text will update.

How can I achieve the expected behavior, update the navigation bar while keeping my observed object working?


Solution

  • The reason is, that

    NavigationLink(destination: DetailView(navigationBarHidden: $navigationBarHidden)) {
        Text("Go Forward")
    }
    

    create new DetailView and so on new DetailViewModel when activating

    try

    import SwiftUI
    
    struct ContentView: View {
        @State var navigationBarHidden = true
        @ObservedObject var viewModel = DetailViewModel()
        var body: some View {
            NavigationView {
                VStack {
                    NavigationLink(destination: DetailView(navigationBarHidden: $navigationBarHidden).environmentObject(viewModel)) {
                        Text("Go Forward")
                    }
                }
                .navigationBarTitle("", displayMode: .inline)
                .navigationBarHidden(navigationBarHidden)
                .onAppear { self.navigationBarHidden = true }
            }
        }
    }
    
    class DetailViewModel: ObservableObject {
        @Published var text = "Didn't work"
    }
    
    struct DetailView: View {
        @Binding var navigationBarHidden: Bool
        @EnvironmentObject var viewModel: DetailViewModel
    
        var body: some View {
            VStack {
                Text(viewModel.text)
            }.onAppear {
                self.navigationBarHidden = false
                self.viewModel.text = "Worked"
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    Now you share the model with DetailView and it works as expected (written)

    If I remove the line self.navigationBarHidden = false, the ObservedObject will work properly and the text will update.

    If you remove this line, the DetailView in not recreated (there is nothing changed in View) State is not part of View state, it is reference type, so SwiftUI don't see any changes until some values which are wrapped by them change.