I've come across the same problem. Here's how i solved it.
- get the scroll offset of the view
- hide or view nav bar according to the offset
1. getting the scroll position
Please see here for how to do this. I'll add a sample code here.
struct ScrollViewOffsetPreferenceKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
import SwiftUI
struct ObservableScrollView<Content>: View where Content : View {
@Namespace var scrollSpace
@Binding var scrollOffset: CGFloat
let content: () -> Content
init(scrollOffset: Binding<CGFloat>,
@ViewBuilder content: @escaping () -> Content) {
_scrollOffset = scrollOffset
self.content = content
}
var body: some View {
ScrollView {
content()
.background(GeometryReader { geo in
let offset = -geo.frame(in: .named(scrollSpace)).minY
Color.clear
.preference(key: ScrollViewOffsetPreferenceKey.self,
value: offset)
})
}
.coordinateSpace(name: scrollSpace)
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
scrollOffset = value
}
}
}
2. hide or view nav bar according to the offset
Now we can use the above created observable scroll view.
@State var scrollOffset: CGFloat = CGFloat.zero
@State var hideNavigationBar: Bool = false
var body: some View {
NavigationView {
ObservableScrollView(scrollOffset: self.$scrollOffset) {
Text("I'm observable")
}
.navigationTitle("Title")
.onChange(of: scrollOffset, perform: { scrollOfset in
let offset = scrollOfset + (self.hideNavigationBar ? 50 : 0) // note 1
if offset > 60 { // note 2
withAnimation(.easeIn(duration: 1), {
self.hideNavigationBar = true
})
}
if offset < 50 {
withAnimation(.easeIn(duration: 1), {
self.hideNavigationBar = false
})
}
})
.navigationBarHidden(hideNavigationBar)
}
}
Note 1: Assume that the height of the navigation title is 50. (This will change depending on the style.) When the nav bar dissapears, scroll offset drops by that height instantly. To keep the offset consistant add the height of the nav bar to the offset if it's hidden.
Note 2: I intentionally let a small difference between two thresholds for hiding and showing instead of using the same value, Because if the user scrolls and keep it in the threshold it won't flicker.