I am trying to persist the PlayerBar view at the bottom of detail screen of the NavigationSplitView across the various views of the NavigationStack when tapping on a Movie item.
Right now, tapping on a movie item and navigating to a detail screen of the movie causes the player bar to be removed as well and is NOT persisted at the bottom of the screen.
Basically, I am trying to go for the mini player behaviour in the iPad version of the Music app.
struct ContentView: View {
let movies = ["Movie 1", "Movie 2", "Movie 3"]
var body: some View {
NavigationSplitView {
Text("Categories goes here")
} detail: {
PlayerBar {
NavigationStack {
List(movies, id: .self) { movie in
NavigationLink {
Text("Movie detail for (movie)")
} label: {
Text(movie)
}
}
.navigationTitle("Movies")
}
}
}
}
}
struct PlayerBar<Content: View>: View {
var content: () -> Content
var body: some View {
ZStack(alignment: .bottom) {
content()
Color.blue
.frame(height: 50)
.clipShape(.rect(cornerRadius: 15))
}
.padding()
}
}
2
Answers
Thanks to Benzy Neez's answer, managed to write a slightly modified version of the answer here where PlayerBar still takes in a content closure but dynamically updates based on the column visibility.
Following answer is compatible with iOS 16 and up.
The only issue I am having right now is that the animation of the player bar expanding and shrinking is not really in sync with the column being dismissed and brought back.
Happy to get any feedback for further improvements.
If you attach the
PlayerBar
to theNavigationSplitView
using.safeAreaInset(edge: .bottom)
then it stays on top of the nestedNavigationStack
. However, it also stays on top of the sidebar when it is expanded, which is not ideal.To work around the problem of the
PlayerBar
covering the sidebar, a mask can be added toPlayerBar
when the sidebar is showing, to mask out the part that is covering the sidebar:The presence of the sidebar can be detected by supplying the
NavigationSplitView
with a binding to the column visibility.The width to be masked out should correspond to the width of the sidebar. This can be measured using an
.onGeometryChange
modifier on the sidebar content.It looks best if the masking change uses an animation that is similar in speed and style to the native animation used for revealing the sidebar. Using
.spring(duration: 0.3)
works quite well.In the updated example below,
PlayerBar
has been changed to a simple view, rather than a generic wrapper for some other content.Ps. You referred to the Music app on iPad – it looks like this is using something more in the style of a
TabView
for the nested detail, rather than aNavigationStack
.