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

SwiftUI实现Apple News式无回弹滚动头部效果

实现Apple News风格的滚动回弹效果

要实现头部滑出视图时无回弹、头部固定时列表正常回弹的效果,不能直接关闭整个ScrollView的回弹,而是需要根据滚动偏移动态控制UIScrollView的回弹属性。以下是具体实现方案:

核心思路

  • 监听ScrollView的滚动偏移,判断头部是否处于视图范围内
  • 当头部滑出(滚动偏移y > 0)时,禁用顶部方向的回弹,但保留底部列表的回弹能力
  • 当头部在视图内(滚动偏移y ≤ 0)时,恢复整体回弹行为

兼容iOS13+的实现代码

使用Introspect获取底层UIScrollView,通过自定义代理监听滚动并动态调整属性:

import SwiftUI
import Introspect

struct TestView: View {
    @State private var scrollOffset: CGFloat = 0
    
    var body: some View {
        ScrollView {
            // 头部组件
            VStack {
                Text("Header content")
            }
            .frame(maxWidth: .infinity)
            .padding(16)
            .background(.white)
            
            // 列表内容
            ForEach(1...100, id: \.self) { value in
                Text("\(value)")
            }
        }
        .frame(maxWidth: .infinity)
        .background(.gray)
        .introspectScrollView { scrollView in
            scrollView.delegate = ScrollDelegate(scrollOffset: $scrollOffset, scrollView: scrollView)
            scrollView.alwaysBounceVertical = true // 始终允许底部回弹
        }
    }
}

// 自定义滚动代理类
class ScrollDelegate: NSObject, UIScrollViewDelegate {
    private let scrollOffset: Binding<CGFloat>
    private weak var scrollView: UIScrollView?
    
    init(scrollOffset: Binding<CGFloat>, scrollView: UIScrollView) {
        self.scrollOffset = scrollOffset
        self.scrollView = scrollView
        super.init()
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        scrollOffset.wrappedValue = scrollView.contentOffset.y
        
        // 根据滚动偏移动态调整回弹属性
        if scrollView.contentOffset.y > 0 {
            // 头部已滑出,禁用顶部回弹
            scrollView.bounces = false
        } else {
            // 头部在视图内,恢复整体回弹
            scrollView.bounces = true
        }
    }
}

iOS17+简化实现

利用SwiftUI原生的scrollPositionAPI替代代理监听,代码更简洁:

import SwiftUI
import Introspect

struct TestView: View {
    @State private var scrollPosition: CGFloat = 0
    
    var body: some View {
        ScrollView {
            VStack { // 头部组件
                Text("Header content")
            }
            .frame(maxWidth: .infinity)
            .padding(16)
            .background(.white)
            
            ForEach(1...100, id: \.self) { value in
                Text("\(value)")
            }
        }
        .frame(maxWidth: .infinity)
        .background(.gray)
        .scrollPosition(id: $scrollPosition, anchor: .top)
        .introspectScrollView { scrollView in
            scrollView.alwaysBounceVertical = true
            // 根据滚动位置动态切换回弹状态
            scrollView.bounces = scrollPosition <= 0
        }
    }
}

效果说明

  • 向上滚动时,头部滑出视图后,继续上拉不会出现顶部回弹
  • 向下滚动回到顶部时,头部完全进入视图后,下拉可以触发正常的顶部回弹
  • 无论头部状态如何,列表底部滚动到底部时都能正常回弹

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 03:17:04