You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI实现带Tab布局的可折叠头部技术求助

解决方案

问题根源

内部垂直ScrollView会优先消费拖动手势,导致外层添加的DragGesture无法接收到上下拖动的事件。

方案1:通过偏移变化直接判断滚动方向(推荐)

你已经在监听offset的变化,完全可以利用这个数据判断滚动方向,不需要额外的DragGesture:

  1. 先定义滚动方向枚举:
enum ScrollDirection {
    case up, down
}
  1. 修改onChange逻辑,直接通过当前偏移和上一次偏移对比判断方向:
struct ScrollingHeaderView: View {
    @State var progress: CGFloat = .zero
    @State var offset: CGPoint = .zero
    @State var previousOffset: CGPoint = .zero
    
    enum ScrollDirection {
        case up, down
    }

    var body: some View {
        ZStack {
            Color.black
            ScrollView(.horizontal) {
                LazyHStack(spacing: .zero) {
                    OffsetObservingScrollView(axes: .vertical, offset: $offset) {
                        // 你的滚动内容
                    }
                    .onChange(of: offset) { newValue in
                        // 判断滚动方向
                        let direction: ScrollDirection = newValue.y < previousOffset.y ? .up : .down
                        print("滚动方向:\(direction)")
                        
                        // 原来的进度计算逻辑
                        let distance = newValue.y - previousOffset.y
                        previousOffset = newValue
                        let progress = distance / 30
                        self.progress = min(max(progress, 0), 1)
                    }
                }
            }
            .scrollTargetBehavior(.paging)
            .scrollIndicators(.hidden)
        }
    }
}

方案2:调整手势优先级以捕获拖动事件

如果一定要用DragGesture,需要将手势直接添加到内部垂直ScrollView上,并使用simultaneousGesture确保手势能和滚动手势同时触发:

OffsetObservingScrollView(axes: .vertical, offset: $offset) {
    // 你的滚动内容
}
.onChange(of: offset) { newValue in
    // 原来的进度计算逻辑
    let distance = newValue.y - previousOffset.y
    previousOffset = newValue
    let progress = distance / 30
    self.progress = min(max(progress, 0), 1)
}
.simultaneousGesture(
    DragGesture(coordinateSpace: .local)
        .onChanged { value in
            let direction = value.translation.height > 0 ? .down : .up
            print("滚动方向:\(direction)")
        }
)

注意这里将coordinateSpace改为.local,更贴合内部滚动视图的坐标系,避免全局坐标系带来的偏差。

额外提示

如果你的OffsetObservingScrollView是基于UIScrollView封装的,还可以直接在UIScrollViewDelegate的scrollViewDidScroll方法中判断滚动方向,这样更直接:

// 先给UIScrollView添加存储上一次偏移的属性
extension UIScrollView {
    private struct AssociatedKeys {
        static var previousOffset = "previousOffset"
    }
    
    var previousContentOffset: CGPoint {
        get { objc_getAssociatedObject(self, &AssociatedKeys.previousOffset) as? CGPoint ?? .zero }
        set { objc_setAssociatedObject(self, &AssociatedKeys.previousOffset, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }
}

// 在Delegate方法中判断方向
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let direction = scrollView.contentOffset.y > scrollView.previousContentOffset.y ? .down : .up
    // 这里可以通过闭包或绑定将方向传递给SwiftUI视图
    scrollView.previousContentOffset = scrollView.contentOffset
}

内容的提问来源于stack exchange,提问作者Stijnk008

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 20:12:52