如何在禁用Bounce的ScrollView中添加下拉刷新功能
方案1:通过UIPanGestureRecognizer监听下拉手势
这种方法不依赖ScrollView的滚动能力,哪怕内容不足以滚动也能触发下拉检测。
- 给ScrollView添加拖拽手势识别器,同时准备自定义刷新视图:
private var panStartY: CGFloat = 0 private let refreshThreshold: CGFloat = 60 // 触发刷新的下拉距离 private var isRefreshing = false private var refreshView: UIActivityIndicatorView! override func viewDidLoad() { super.viewDidLoad() scrollView.bounces = false // 添加自定义刷新视图到ScrollView顶部 refreshView = UIActivityIndicatorView(style: .medium) refreshView.frame = CGRect(x: scrollView.center.x - 15, y: -60, width: 30, height: 30) scrollView.addSubview(refreshView) // 添加拖拽手势 let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) panGesture.delegate = self scrollView.addGestureRecognizer(panGesture) }
- 实现手势处理逻辑:
@objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: scrollView) switch gesture.state { case .began: panStartY = scrollView.contentOffset.y case .changed: // 仅处理从顶部开始的下拉动作 if panStartY <= 0 && translation.y > 0 { let pullDistance = min(translation.y, refreshThreshold) // 让刷新视图跟随下拉动作移动 refreshView.transform = CGAffineTransform(translationX: 0, y: pullDistance) // 达到阈值时标记为准备刷新状态 if pullDistance >= refreshThreshold && !isRefreshing { isRefreshing = true refreshView.startAnimating() } } case .ended, .cancelled: if isRefreshing { // 触发刷新逻辑 performRefresh() // 固定刷新视图在顶部位置 UIView.animate(withDuration: 0.3) { self.refreshView.transform = CGAffineTransform(translationX: 0, y: self.refreshThreshold) } } else { // 未达到阈值,回弹隐藏刷新视图 UIView.animate(withDuration: 0.3) { self.refreshView.transform = .identity } } default: break } } // 解决手势冲突,允许ScrollView原有手势和自定义手势同时识别 extension YourViewController: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
- 刷新完成后恢复状态:
private func performRefresh() { // 这里替换为你的实际刷新逻辑,比如网络请求 DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.endRefreshing() } } private func endRefreshing() { isRefreshing = false refreshView.stopAnimating() UIView.animate(withDuration: 0.3) { self.refreshView.transform = .identity } }
方案2:动态调整ScrollView的contentSize
让ScrollView始终具备微小的滚动能力,这样scrollViewDidScroll就能触发,同时保持bounces = false。
- 初始化时设置contentSize:
scrollView.bounces = false // 确保contentHeight至少比ScrollView高度大1pt,保证能触发滚动事件 let contentHeight = yourActualContentHeight // 比如tableView.contentSize.height或自定义内容高度 scrollView.contentSize = CGSize(width: scrollView.bounds.width, height: max(scrollView.bounds.height + 1, contentHeight))
- 在
scrollViewDidScroll中检测下拉偏移:
private let refreshThreshold: CGFloat = -60 private var isRefreshing = false func scrollViewDidScroll(_ scrollView: UIScrollView) { // 下拉到阈值位置且未在刷新状态时,触发刷新 if !isRefreshing && scrollView.contentOffset.y <= refreshThreshold { isRefreshing = true beginRefreshing() } } private func beginRefreshing() { // 固定ScrollView偏移,显示刷新状态 UIView.animate(withDuration: 0.3) { self.scrollView.contentOffset = CGPoint(x: 0, y: self.refreshThreshold) } // 执行刷新逻辑 performRefresh() } private func endRefreshing() { isRefreshing = false // 恢复ScrollView偏移 UIView.animate(withDuration: 0.3) { self.scrollView.contentOffset = .zero } } private func performRefresh() { // 替换为你的实际刷新逻辑 DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.endRefreshing() } }
注意:这种方法需要确保contentSize始终大于ScrollView的bounds高度,哪怕内容为空也要设置成scrollView.bounds.height + 1,否则scrollViewDidScroll还是不会触发。
内容的提问来源于stack exchange,提问作者Hiral Jotaniya
相关产品推荐
相关产品推荐

