@EnvironmentObject inside NavigationSplitView not available

As a developer, I have been using SwiftUI to build applications, and I have come across a specific scenario that seems to be causing some unexpected behaviour. When utilizing the @EnvironmentObject property wrapper to pass an object through the view hierarchy, I noticed that it becomes inaccessible after pushing and presenting the second view inside a NavigationStack. (which is inside a NavigationSplitView).

To illustrate the problem, consider the following scenario:

  1. I have a Router class that conforms to ObservableObject, and it holds a NavigationPath to manage navigation within the app.
  2. The SettingsView is a SwiftUI view that uses NavigationStack to display different views based on user interactions. Inside this view, I use @EnvironmentObject to pass the Router object.
  3. Within the SettingsView, I have a button that, when clicked, programmatically pushes a new destination (AdvancedView) to the Router object using router.push(.advanced).
  4. In the AdvancedView I have a Button and a NavigationLink which both should lead to LogsView.
  5. When tapping on the Button (which programmatically pushes a new destination to the NavigationPath) the app crashes with the error that the @EnvironmentObject is not acccessbile.
  6. When tapping on the NavigationLink (which SwiftUI handles by itself) the app does NOT crash and can successfully push the value (.logs) to the NavigationPath (meaning it indeed has access to the @EnvironmentObject).

The issue arises when I use the manually created button to navigate to the LogsView. In this scenario, the Router object, which was previously accessible through the @EnvironmentObject, becomes inaccessible on the AdvancedView. However, when I use the NavigationLink, the Router object is still accessible, and everything works as expected.

To summarize:

  • When using NavigationLink, the @EnvironmentObject (Router in this case) is accessible on the destination view (LogsView).
  • When manually navigating using a button, the @EnvironmentObject (Router) is not accessible on the destination view (LogsView) after pushing and presenting the second view.
//
//  ContentView.swift
//  ViewTester
//
//  Created by Luca Archidiacono on 27.03.23.
//

import SwiftUI

struct ListItem: Identifiable {
    let id = UUID()
    let title: String
    let description: String
}

enum MenuItem: Identifiable {
    case settings

    var id: Self {
        return self
    }

    var title: String {
        switch self {
        case .settings:
            return "settings"
        }
    }
}

struct ContentView: View {
    let menuItems: [MenuItem] = [.settings]
    @State var selected: MenuItem? = .settings

    var body: some View {
        NavigationSplitView {
            List(menuItems, selection: $selected) { selected in
                Text(selected.title)
            }
            .navigationTitle("Menu")
        } detail: {
            if let selected = selected {
                switch selected {
                case .settings:
                    SettingsView()
                }
            }
        }
    }
}

enum Destination: Hashable {
    case advanced
    case logs
}

final class Router: ObservableObject {
    @Published var path = NavigationPath()

    func push(_ destination: Destination) {
        path.append(destination)
    }
}

struct SettingsView: View {
    @StateObject private var router = Router()

    var body: some View {
        NavigationStack(path: $router.path) {
            ZStack {
                Button("Advanced") {
                    router.push(.advanced)
                }
            }
            .navigationDestination(for: Destination.self) { destination in
                switch destination {
                case .advanced:
                    AdvancedView()
                case .logs:
                    LogsView()
                }
            }
        }
        .environmentObject(router)
    }
}

struct AdvancedView: View {
    @EnvironmentObject private var router: Router

    var body: some View {
        // 1. This does not work somehow eventhough we passed router as EnvironmentObject on the NavigationStack.
        Button("Manual Logs") {
            router.push(.logs)
        }
        // 2. This somehow works, and it even pushes to the NavigationPath, which means SwiftUI should know about Router as EnvironmentObject
        NavigationLink(value: Destination.logs) {
            Text("NavigationLink Logs")
        }
    }
}

struct LogsView: View {
    var body: some View {
        Text("Logs")
    }
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
@EnvironmentObject inside NavigationSplitView not available
 
 
Q