0

I have a complex view that includes a ScrollView and I'm trying to hide both the tabBar and the navigationBar whenever the user starts scrolling, and show them again when the user stops scrolling (kind of like in the Apollo iOS for Reddit app).

However, it doesn't work and I'm sure why. Here's my code:

struct View: View {
  @State var isDragging: Bool = false
  @AppStorage("hideTopBarAndNavBarWhenScrolling") var hideTopBarAndNavBarWhenScrolling: Bool = false

  ScrollView {
    LazyVStack(spacing: 0) {
      ...
    }
  }
  .hideNavBarAndTopBar(isDragging, hideTopBarAndNavBarWhenScrolling)
  .simultaneousGesture(dragGesture)
}

  struct HideNavBarAndTopBarModifier: ViewModifier {
    var isScrollViewDragging: Bool
    var hideTopBarAndNavBarWhenScrolling: Bool

    func body(content: Content) -> some View {
        if hideTopBarAndNavBarWhenScrolling {
            Spacer()
                .frame(height: 1)
                .ignoresSafeArea()
            content
                .toolbar(isScrollViewDragging ? .visible : .hidden, for: .tabBar)
                .toolbar(isScrollViewDragging ? .visible : .hidden, for: .navigationBar)
            Spacer()
                .frame(height: 1)
                .ignoresSafeArea()
        } else {
            content
                .toolbar(.visible, for: .tabBar)
                .toolbar(.visible, for: .navigationBar)
        }
    }
}

extension View {
    func hideNavBarAndTopBar(_ isScrollViewDragging: Bool, _ hideTopBarAndNavBarWhenScrolling: Bool) -> some View {
        self.modifier(HideNavBarAndTopBarModifier(isScrollViewDragging: isScrollViewDragging, hideTopBarAndNavBarWhenScrolling: hideTopBarAndNavBarWhenScrolling))
    }
}
}
halfer
  • 19,824
  • 17
  • 99
  • 186
noloman
  • 11,411
  • 20
  • 82
  • 129

1 Answers1

1

I created some demo where you can show or hide toolbars based on scroll direction & scroll range . Numbers are experimental you can find your sweetspot.

struct ContentView: View {
    var body: some View {
        NavigationStack {
            TabView {
                ScrollableView()
            }
            .navigationTitle("Demo")
        }
        
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}



struct ScrollableView: View {
    @State var isHiding : Bool = false
    @State var scrollOffset : CGFloat = 0
    @State var threshHold : CGFloat = 0
    var body: some View {
        ScrollView {
            ZStack {
                LazyVStack {
                    ForEach(0..<300) { _ in
                        ScrollItem()
                    }
                }
                GeometryReader { proxy in
                    Color.clear
                        .changeOverlayOnScroll(
                            proxy: proxy,
                            offsetHolder: $scrollOffset,
                            thresHold: $threshHold,
                            toggle: $isHiding
                        )
                }
            }
        }
        .coordinateSpace(name: "scroll")
        .toolbar(isHiding ? .hidden : .visible, for: .navigationBar)
        .toolbar(isHiding ? .hidden : .visible, for: .tabBar)
        
    }
    
    
 // ScrollChild
struct ScrollItem: View {
        var body: some View {
            Rectangle()
                .fill(Color.red)
                .frame(minHeight: 200)
        }
    }
}





extension View {
    
    func changeOverlayOnScroll(
        proxy : GeometryProxy,
        offsetHolder : Binding<CGFloat>,
        thresHold : Binding<CGFloat>,
        toggle: Binding<Bool>
    ) -> some View {
        self
            .onChange(
                of: proxy.frame(in: .named("scroll")).minY
            ) { newValue in
                // Set current offset
                offsetHolder.wrappedValue = abs(newValue)
                // If current offset is going downward we hide overlay after 200 px.
                if offsetHolder.wrappedValue > thresHold.wrappedValue + 200 {
                    // We set thresh hold to current offset so we can remember on next iterations.
                    thresHold.wrappedValue = offsetHolder.wrappedValue
                    // Hide overlay
                    toggle.wrappedValue = true
                    
                    // If current offset is going upward we show overlay again after 200 px
                }else if offsetHolder.wrappedValue < thresHold.wrappedValue - 200 {
                    // Save current offset to threshhold
                    thresHold.wrappedValue = offsetHolder.wrappedValue
                    // Show overlay
                    toggle.wrappedValue = false
                }
         }
    }
}