Berry Blue
Berry Blue

Reputation: 16522

How to hide navigation bar title and buttons in SwiftUI

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

Answers (1)

Yrb
Yrb

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

Related Questions