Reputation: 16522
I have a view like this with a List in a NavigationStack.
struct ContentView: View {
var body: some View {
TabView {
NavigationStack {
List {
HStack {
Spacer()
Text("Header")
Spacer()
}
.frame(height: 300)
.background(.red)
.listRowInsets(EdgeInsets())
ForEach(1..<100) {
Text("\($0)")
}
}
.listStyle(.plain)
.ignoresSafeArea(.all, edges: .top)
.navigationTitle("Navigation Title")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
It ignores the safe area only at the top. When you scroll up the navigation bar will appear. I want the navigation bar title and potential navigation buttons to only appear when the navigation bar is visible when you are scrolling. Is this possible to do in SwiftUI?
Upvotes: 0
Views: 491
Reputation: 9725
As you are probably aware by the dearth of answers, you can't do this using Apple's stock . navigationTitle
. However, you do have access to the safeAreaInsets
for placing views, so you can place your title and buttons in there. The other part of the functionality is to make this appear ONLY when your view is not scrolled. For that, you need to use a GeometryReader
and a PreferenceKey
. Here is a comment solution for all of this. Obviously, the view is rudimentary:
struct ContentView: View {
// you want to compare against the scrolling list. This is an identifier for that coordinate space
let coordinateSpace = "list"
@State var isAtTop = true
var body: some View {
TabView {
NavigationStack {
List {
HStack {
Spacer()
Text("Header")
Spacer()
}
.frame(height: 300)
.background(
// Since this sits at the top of your list, we will put the GeometryReader here
// This will read where the view is in relationship to the list
GeometryReader { proxy in
// Since you were already putting a color in the background, I used it.
// Otherwise, I would have used Color.clear here
Color.red
.preference( // Read the location
key: PreferenceKey.self,
// this reads the location relative to the list. All we are passing is the origin
value: proxy.frame(in: .named(coordinateSpace)).origin
)
}
)
.listRowInsets(EdgeInsets())
ForEach(1..<100) {
Text("\($0)")
}
}
// This identifies the view to compare against, in this case the list
.coordinateSpace(name: coordinateSpace)
// When the PreferenceKey changes, this updates
.onPreferenceChange(PreferenceKey.self) { position in
// If the view is not scrolled, or is pulled against the spring we return true
// but as soon as it is scrolled up at all, we return false.
if position.y >= 0 {
isAtTop = true
} else {
isAtTop = false
}
}
.listStyle(.plain)
.ignoresSafeArea(.all, edges: .top)
// This is how you put a view into a safe area inset
.safeAreaInset(edge: .top) {
if !isAtTop {
VStack {
Text("Navigation Title")
}
.frame(maxWidth: .infinity)
.background(.ultraThinMaterial)
}
}
}
}
}
struct PreferenceKey: SwiftUI.PreferenceKey {
static var defaultValue: CGPoint { .zero }
// reduce is required, but we aren't using it. We just want to see the value
static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {}
}
}
Upvotes: 1