'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.
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
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)
}
}
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 |

