Push new views to sidebar when using NavigationPath

I have an existing app that uses NavigationSplitView with a detail view that is never changed and just updates to show changes in data. All navigation changes the sidebar only using NavigationLinks with .isDetailLink(false).

Now I'm wanting to use NavigationPath with a simple router and enums.

import SwiftUI

@main
struct NavRouterApp: App {
	@State private var router = Router()

	var body: some Scene {
		WindowGroup {
			NavigationStack(path: $router.navPath) {
				ContentView()
					.navigationDestination(for: AppRoute.self) { route in
						switch route {
						case .citizens:
							ContentView()
								.environment(router)
						case .citizen:
							EmptyView()
								.environment(router)
						case .tasform2:
							TASForm2View()
								.environment(router)
						case .start:
							StartView()
								.environment(router)
						case .editcharacteristics:
							EmptyView()
								.environment(router)
						case .basicdata(let citizen):
							BasicDataView(citizen: citizen)
								.environment(router)
								.navigationBarBackButtonHidden(true)
						case .characteristics:
							EmptyView()
								.environment(router)
						}
					}
			}
			.environment(router)
		}
	}
}

The problem is that pushing a subview now replaces the entire content instead of just the sidebar.

Pushing a subview with NavigationSplitView would update the sidebar as desired but I would have to replace the detail view, which is not a good idea.

I haven't been able to find any way to accomplish what I want. Suggestions?

Answered by DTS Engineer in 894591022

Hello @agorski,

Thank you for your post.

SwiftUI's NavigationSplitView is designed to catch and route links to content or detail. If using these columns don't work for you, create a sidebar that manages its own state:

struct SidebarView: View {
    @State private var selected: ...

Or use a different navigation design pattern that does not route links to detail. In my testing, I was able to get a TabView or the following code to code to work:

enum Item: String, CaseIterable {
    case indigoView, tealView, orangeView, pinkView, greenView, purpleView, yellowView

    var color: Color {
        switch self {
        case .indigoView: .indigo
        case .tealView: .teal
        case .orangeView: .orange
        case .pinkView: .pink
        case .greenView: .green
        case .purpleView: .purple
        case .yellowView: .yellow
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationSplitView {
            SidebarView()
        } detail: {
            ContentUnavailableView("Static Detail View", systemImage: "paintpalette")
        }
    }
}

struct SidebarView: View {
    var body: some View {
        NavigationStack {
            List(Item.allCases, id: \.self) { item in
                NavigationLink(item.rawValue, value: item)
            }
            .navigationDestination(for: Item.self) { item in
                ItemView(item: item)
            }
        }
    }
}

struct ItemView: View {
    let item: Item

    var body: some View {
        ZStack {
            Color(item.color)
                .ignoresSafeArea()
        }
    }
}

You might find helpful information about designing user experiences for Apple platforms in the in Human Interface Guidelines.

Lastly, if you'd like us to consider adding the necessary functionality, please file an enhancement request using Feedback Assistant. Once you file the request, please post the FB number here.

If you're not familiar with how to file enhancement requests, take a look at Bug Reporting: How and Why?

 Travis

Speaking from memory:

I believe there is a navigationSplitView initializer that lets you specify each of the three columns manually.

Maybe try dropping NavigationStacks in each of those spots to have more navigation control over them?

It’s still an odd navigation experience, though. You may need to make your own!

Hello @agorski,

Thank you for your post.

SwiftUI's NavigationSplitView is designed to catch and route links to content or detail. If using these columns don't work for you, create a sidebar that manages its own state:

struct SidebarView: View {
    @State private var selected: ...

Or use a different navigation design pattern that does not route links to detail. In my testing, I was able to get a TabView or the following code to code to work:

enum Item: String, CaseIterable {
    case indigoView, tealView, orangeView, pinkView, greenView, purpleView, yellowView

    var color: Color {
        switch self {
        case .indigoView: .indigo
        case .tealView: .teal
        case .orangeView: .orange
        case .pinkView: .pink
        case .greenView: .green
        case .purpleView: .purple
        case .yellowView: .yellow
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationSplitView {
            SidebarView()
        } detail: {
            ContentUnavailableView("Static Detail View", systemImage: "paintpalette")
        }
    }
}

struct SidebarView: View {
    var body: some View {
        NavigationStack {
            List(Item.allCases, id: \.self) { item in
                NavigationLink(item.rawValue, value: item)
            }
            .navigationDestination(for: Item.self) { item in
                ItemView(item: item)
            }
        }
    }
}

struct ItemView: View {
    let item: Item

    var body: some View {
        ZStack {
            Color(item.color)
                .ignoresSafeArea()
        }
    }
}

You might find helpful information about designing user experiences for Apple platforms in the in Human Interface Guidelines.

Lastly, if you'd like us to consider adding the necessary functionality, please file an enhancement request using Feedback Assistant. Once you file the request, please post the FB number here.

If you're not familiar with how to file enhancement requests, take a look at Bug Reporting: How and Why?

 Travis

Feel free to provide more information about your exact goal. This way we can suggest a more relevant workaround.

 Travis

Thanks for the suggestions. As I mentioned above, I'm trying to persist the same detail view when navigating, and not replace it. The only way I found to do this was to use NavigationLink with .isDetailLink(false).

But when using a NavigationPath approach, NavigationLinks push the view onto the stack but the view doesn't appear. If I then push another view using the router(which does appear) and navigate back, then the view I pushed with the NavigationLink is revealed.

// Destination view does not appear but is pushed to stack
NavigationLink(
    destination: {
        BasicDataView(citizen: "johndoe")
            .navigationBarBackButtonHidden(true)
									},
    label: {
        Text("Character Generation")
    }
)
.isDetailLink(false)  // Keep in sidebar
// Works correctly but replaces entire content, or if pushed view uses NavigationSplitView then replaces the detail view which is not desirable
Button(action: {
    router.push(route: .basicdata(citizen: "johndoe"))
}) {
    Text("Character Generation")
}
Push new views to sidebar when using NavigationPath
 
 
Q