英文:
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("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
}
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论