SwiftUI:在滚动时隐藏 tabBar 和 navigationBar

huangapple go评论109阅读模式
英文:

SwiftUI: Hiding tabBar and navigationBar on scroll

问题

我有一个包含 ScrollView 的复杂视图,我试图在用户开始滚动时隐藏 tabBar 和 navigationBar,当用户停止滚动时再显示它们(有点像 Reddit 应用程序中的 Apollo iOS 那样)。

但是,它不起作用,我不确定为什么。这是我的代码:

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))
    }
}
}
英文:

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))
    }
}
}

答案1

得分: 1

我创建了一些演示,您可以根据滚动方向和滚动范围显示或隐藏工具栏。数字是实验性的,您可以找到适合您的最佳配置。

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

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
                // 设置当前偏移量
                offsetHolder.wrappedValue = abs(newValue)
                // 如果当前偏移量向下,我们在200像素后隐藏覆盖层。
                if offsetHolder.wrappedValue > thresHold.wrappedValue + 200 {
                    // 我们将阈值设置为当前偏移量,以便我们可以在下一次迭代时记住。
                    thresHold.wrappedValue = offsetHolder.wrappedValue
                    // 隐藏覆盖层
                    toggle.wrappedValue = true
                    
                    // 如果当前偏移量向上,我们在200像素后再次显示覆盖层
                } else if offsetHolder.wrappedValue < thresHold.wrappedValue - 200 {
                    // 将当前偏移量保存到阈值
                    thresHold.wrappedValue = offsetHolder.wrappedValue
                    // 显示覆盖层
                    toggle.wrappedValue = false
                }
         }
    }
}
英文:

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(&quot;Demo&quot;)
        }
        
    }
}

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..&lt;300) { _ in
                        ScrollItem()
                    }
                }
                GeometryReader { proxy in
                    Color.clear
                        .changeOverlayOnScroll(
                            proxy: proxy,
                            offsetHolder: $scrollOffset,
                            thresHold: $threshHold,
                            toggle: $isHiding
                        )
                }
            }
        }
        .coordinateSpace(name: &quot;scroll&quot;)
        .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&lt;CGFloat&gt;,
        thresHold : Binding&lt;CGFloat&gt;,
        toggle: Binding&lt;Bool&gt;
    ) -&gt; some View {
        self
            .onChange(
                of: proxy.frame(in: .named(&quot;scroll&quot;)).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 &gt; 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 &lt; thresHold.wrappedValue - 200 {
                    // Save current offset to threshhold
                    thresHold.wrappedValue = offsetHolder.wrappedValue
                    // Show overlay
                    toggle.wrappedValue = false
                }
         }
    }
}

huangapple
  • 本文由 发表于 2023年6月26日 02:04:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76551783.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定