Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EnvironmentObject not injected for .navigationDestination

Tags:

swift

swiftui

Consider code

@EnvironmentObject var navModel: NavigationModel
var body: some View {
    someView
       .navigationDestination(for: ImageModel.self) { imageModel in
                ImageDetailedView(image: imageModel)
                    .environmentObject(navModel)   //this is required 
       }
}

Is navigation not considered a child of the view? And if so, is it normal to keep throwing around environemntObjects around the navigation stack?

import Combine
import SwiftUI

enum Destination {
    case firstPage
    case secondPage
}

enum Category: Int, Hashable, CaseIterable, Identifiable, Codable {
    case dessert
    case pancake
    case salad
    case sandwich
    
    var id: Int { rawValue }
    
    var localizedName: LocalizedStringKey {
        switch self {
        case .dessert:
            return "Dessert"
        case .pancake:
            return "Pancake"
        case .salad:
            return "Salad"
        case .sandwich:
            return "Sandwich"
        }
    }
}

@available(iOS 16.0, *)
class Coordinator3: ObservableObject {
    @Published var path = NavigationPath()
    
    func gotoHomePage() {
        path.removeLast(path.count)
    }
    
    func tapOnEnter() {
        path.append(Destination.firstPage)
    }
    
    func tapOnFirstPage() {
        path.append(Destination.secondPage)
    }
    
    func tapOnSecondPage() {
        path.removeLast()
    }
    
    func test() {
        path.removeLast(path.count)
        path.append(2)
    }
}

class Test :ObservableObject {
    var name = "test"
}

@available(iOS 16.0, *)
struct SplitTestView: View {
    @State var selectedCategory: Category?
    var categories = Category.allCases
    @ObservedObject var coordinator = Coordinator3()
    @StateObject var test = Test()
    
    var body: some View {
        NavigationSplitView {
            List(categories, selection: $selectedCategory) { category in
                NavigationLink(category.localizedName, value: category)
            }
        } detail: {
            NavigationStack(path: $coordinator.path) {
                switch selectedCategory {
                case .dessert:
                    Text(selectedCategory!.localizedName)
                case .pancake:
                    VStack {
                        Text("Navigation stack")
                            .padding()
                        NavigationLink("NavigationLink to enter first page", value: Destination.firstPage)
                            .padding()
                        NavigationLink("NavigationLink to enter second page", value: Destination.secondPage)
                            .padding()
                    }
                    .navigationDestination(for: Destination.self) { destination in
                        if destination == .firstPage {
                            FirstPage()
                        } else {
                            Text(
                                "SecondPage()"
                            )
                        }
                    }
                case .salad: Text(selectedCategory!.localizedName)
                case .sandwich: Text(selectedCategory!.localizedName)
                case .none: Text("hi")
                }
            }.environmentObject(test)
        }
    }
}

@available(iOS 16.0, *)
struct SplitTestView_Previews: PreviewProvider {
    static var previews: some View {
        SplitTestView()
    }
}
struct FirstPage: View {
    @EnvironmentObject var test: Test
    var body: some View {
        Text("First Page \(test.name)")
    }
}
like image 941
erotsppa Avatar asked Apr 28 '26 17:04

erotsppa


1 Answers

This is why MREs are important that is why I mentioned it in my first comment, you introduced NavigationSplitView.

Scenario 1

If you are using NavigationSplitView you have to inject the EnvironmentObject to the NavigationSplitView.

NavigationSplitView{
    /*other stuff that includes a navigationDestination*/
}.environmentObject(navModel)

Scenerio 2

When working with just NavigationStack you have to inject on the NavigationStack

NavigationStack{
    /*other stuff that includes a navigationDestination*/
}.environmentObject(navModel)

Scenerio 3 - Deprecated

When working with just NavigationView you have to inject on the NavigationView

NavigationView{
    /*other stuff that includes a NavigationLink*/
}.environmentObject(navModel)

Your Sample

Just move the injection code one line down.

import Combine
import SwiftUI

@available(iOS 16.0, *)
struct SplitTestView: View {
    @State var selectedCategory: Category?
    var categories = Category.allCases
    @StateObject var coordinator = Coordinator3() //<-- Switch to StateObject
    @StateObject var test = Test()
    
    var body: some View {
        NavigationSplitView {
            List(categories, selection: $selectedCategory) { category in
                NavigationLink(category.localizedName, value: category)
            }
        } detail: {
            NavigationStack(path: $coordinator.path) {
                switch selectedCategory {
                case .dessert:
                    Text(selectedCategory!.localizedName)
                case .pancake:
                    VStack {
                        Text("Navigation stack")
                            .padding()
                        NavigationLink("NavigationLink to enter first page", value: Destination.firstPage)
                            .padding()
                        NavigationLink("NavigationLink to enter second page", value: Destination.secondPage)
                            .padding()
                    }
                    .navigationDestination(for: Destination.self) { destination in
                        if destination == .firstPage {
                            FirstPage()
                        } else {
                            Text(
                                "SecondPage()"
                            )
                        }
                    }
                case .salad: Text(selectedCategory!.localizedName)
                case .sandwich: Text(selectedCategory!.localizedName)
                case .none: Text("hi")
                }
            }
        }.environmentObject(test) //<<--- Add to the NavigationSplitView - The NavigationLink's are presenting in a separate column than the Stack, the only thing they share is the split view.
    }
}
enum Destination {
    case firstPage
    case secondPage
}

enum Category: Int, Hashable, CaseIterable, Identifiable, Codable {
    case dessert
    case pancake
    case salad
    case sandwich
    
    var id: Int { rawValue }
    
    var localizedName: LocalizedStringKey {
        switch self {
        case .dessert:
            return "Dessert"
        case .pancake:
            return "Pancake"
        case .salad:
            return "Salad"
        case .sandwich:
            return "Sandwich"
        }
    }
}

@available(iOS 16.0, *)
class Coordinator3: ObservableObject {
    @Published var path = NavigationPath()
    
    func gotoHomePage() {
        path.removeLast(path.count)
    }
    
    func tapOnEnter() {
        path.append(Destination.firstPage)
    }
    
    func tapOnFirstPage() {
        path.append(Destination.secondPage)
    }
    
    func tapOnSecondPage() {
        path.removeLast()
    }
    
    func test() {
        path.removeLast(path.count)
        path.append(2)
    }
}

class Test :ObservableObject {
    var name = "test"
}



@available(iOS 16.0, *)
struct SplitTestView_Previews: PreviewProvider {
    static var previews: some View {
        SplitTestView()
    }
}
struct FirstPage: View {
    @EnvironmentObject var test: Test
    var body: some View {
        Text("First Page \(test.name)")
    }
}

Addl Info

In the Migration Guide apple talks about the differences between the 2 types.

https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types

They call the inside of the NavigationStack "content"

NavigationStack {
    /* content */
}

And the inside of the NavigationSplitView "columns"

NavigationSplitView {
    /* column 1 */
} content: {
    /* column 2 */
} detail: {
    /* column 3 */
}

In their respective setups the "columns" and "content" only share the NavigationSplitView or NavigationStack respectively with the NavigationLinks/navigationDestination.

A NavigationLink inside NavigationStack that is inside NavigationSplitView presents in its own column.

The injection should always happen at the uppermost shared View.

like image 77
lorem ipsum Avatar answered May 01 '26 14:05

lorem ipsum



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!