'How to navigate between NavigationLink while leave a part of main window stay the same in SwiftUI

I would like to navigate between different NavigationLinks in NavigationView while some part of the main window stay the same. For example, I want to make a music app and I want to let the play controller always on top, while I can display different navigation contents (songs page, artists page...) using the rest of the window.

Like what's showed in the picture below, I want to keep the red part always there while the blue part changes.

Navigation Example Picture

My code would be like below, but it won't work correctly. The AlwaysStayView() disappears when I click any NavigationLink on sidebar. So, how can I correct it or is there any solution (prefer in SwiftUI, but framework like UIKit would also be OK). I would appreciate it.

NavigationView {
            List {
                NavigationLink { DiscoverView() }
                    label: { Label("Discover", systemImage: "magnifyingglass") }
                NavigationLink { SongsView() }
                    label: { Label("Songs", systemImage: "music.note") }
                NavigationLink { ArtistsView() }
                    label: { Label("Artists", systemImage: "music.mic") }
                }
            }
            .listStyle(SidebarListStyle())

            VStack {
                AlwaysStayView()
                SongsView()
            }
}


Solution 1:[1]

In this case the default details view and navigated details view should be the same, but updated content can be injected in it in navigation link.

Here is a demo. Tested with Xcode 13.3 / iPadOS 15.4

demo

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink { DetailsView { DiscoverView() } }
                  label: { Label("Discover", systemImage: "magnifyingglass") }
                NavigationLink { DetailsView { SongsView() } }
                  label: { Label("Songs", systemImage: "music.note") }
                NavigationLink { DetailsView { ArtistsView() } }
                  label: { Label("Artists", systemImage: "music.mic") }
            }
            .navigationBarHidden(true)
            .listStyle(SidebarListStyle())

            DetailsView { SongsView() }  // << here default !!
        }
    }
}

struct DetailsView<V: View>: View {
    @ViewBuilder var content: () -> V  // << injected via builder !!
    var body: some View {
        VStack(spacing: 0) {
            AlwaysStayView()
            content()          // << changed part here !!
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
        .navigationBarHidden(true)
    }
}

backup

Solution 2:[2]

The NavigationLink from sidebar always exchanges the whole right screen area. So you would have to put your AlwaysStayView inside the navigation links – in each. Either on top level or inside the respective detail views. Here is one example:

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink {
                    DetailView(title: "Always stay", color: .red).frame(height: 100)
                    DetailView(title: "Discover", color: .blue) }
                    label: { Label("Discover", systemImage: "magnifyingglass") }
                
                NavigationLink {
                    DetailView(title: "Always stay", color: .red).frame(height: 100)
                    DetailView(title: "Songs", color: .teal) }
                    label: { Label("Songs", systemImage: "music.note") }
                
                NavigationLink {
                    DetailView(title: "Always stay", color: .red).frame(height: 100)
                    DetailView(title: "Artists", color: .mint) }
                    label: { Label("Artists", systemImage: "music.mic") }
            }
            .listStyle(.sidebar)
            
            // Standard view if no item is lelected
            VStack {
                DetailView(title: "Always stay", color: .red).frame(height: 100)
                DetailView(title: "Songs", color: .teal)
            }
        }
        .toolbar {
            ToolbarItem(placement: .principal) {
                Text("Toolbar")
            }
        }
    }
}


struct DetailView: View {
    
    let title: String
    let color: Color
    
    var body: some View {
        Text(title).font(.title)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(color)
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 ChrisR