blub
blub

Reputation: 692

Hide navigation bar on scroll in SwiftUI?

Hiding the navigation bar on scroll was supported in Swift with navigationController?.hidesBarsOnSwipe = true

To be clear, I'd like it to only be hidden on scroll, so .navigationBarHidden(true) would not suffice.

I tried accessing the NavigationController as described in this Stackoverflow answer, (I added nc.hidesBarsOnSwipe = true) and while it compiled, it did not work.

Is this supported in SwiftUI?

Upvotes: 10

Views: 11923

Answers (2)

Lakshith Nishshanke
Lakshith Nishshanke

Reputation: 136

I've come across the same problem. Here's how i solved it.

  1. get the scroll offset of the view
  2. 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.

Upvotes: 1

arsenius
arsenius

Reputation: 13246

NavigationView seems to be relatively buggy still. For example, by default a ScrollView will ignore the title area and just scroll beneath it.

It looks to me like you can get this working by using displayMode: .inline and StackNavigationViewStyle() together.

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                ForEach(0...20, id: \.self) { count in
                    (count % 2 == 0 ? Color.red : Color.blue)
                        .frame(height: 44.0)
                }
            }
            .background(NavigationConfigurator { nc in // NavigationConfigurator is from the OP's post: https://stackoverflow.com/a/58427754/7834914
                nc.hidesBarsOnSwipe = true
            })
            .navigationBarTitle("Hello World", displayMode: .inline)
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

Before Scroll: Before Scroll

After Scroll: After Scroll

Upvotes: 3

Related Questions