如何实现UICollectionView左滑无限制,临时禁用右滑选中答案后恢复?
实现UICollectionView的滑动限制:必须完成当前页作答才能右滑
这个需求在答题类APP里很常见,我之前做类似项目时也遇到过,核心思路是借助UICollectionView的父类UIScrollView的代理方法,拦截滚动行为并区分滑动方向——针对向右滑动(进入下一页)做权限校验,向左滑动则完全放开限制。
核心实现步骤
- 开启UICollectionView的分页模式,确保每页占满屏幕
- 维护当前页码和每页的作答状态
- 通过UIScrollView代理方法拦截滚动,判断方向并校验权限
- 用户完成作答后更新状态,允许向右滑动
完整代码示例
import UIKit // 自定义Cell的回调协议,用于通知ViewController用户选中了答案 protocol AnswerCellDelegate: AnyObject { func userDidSubmitAnswer(in cell: AnswerCollectionViewCell) } class AnswerCollectionViewCell: UICollectionViewCell { weak var delegate: AnswerCellDelegate? // 假设这是用户选中答案的按钮或交互控件的点击事件 @IBAction func selectAnswerButtonTapped(_ sender: UIButton) { delegate?.userDidSubmitAnswer(in: self) } } class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate, AnswerCellDelegate { @IBOutlet weak var collectionView: UICollectionView! // 总页数,替换成你的实际页数 private let totalPages = 5 // 记录每页是否已作答 private var pageAnswerStatus: [Bool] = [] // 当前页码 private var currentPageIndex = 0 override func viewDidLoad() { super.viewDidLoad() // 初始化作答状态数组,默认所有页未作答 pageAnswerStatus = Array(repeating: false, count: totalPages) // 配置CollectionView collectionView.delegate = self collectionView.dataSource = self collectionView.isPagingEnabled = true collectionView.showsHorizontalScrollIndicator = false // 设置FlowLayout,确保每个Cell占满屏幕 let flowLayout = UICollectionViewFlowLayout() flowLayout.itemSize = view.bounds.size flowLayout.minimumLineSpacing = 0 flowLayout.scrollDirection = .horizontal collectionView.collectionViewLayout = flowLayout // 注册Cell(如果是Storyboard创建可忽略) collectionView.register(AnswerCollectionViewCell.self, forCellWithReuseIdentifier: "AnswerCell") } // MARK: - UICollectionView DataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return totalPages } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AnswerCell", for: indexPath) as! AnswerCollectionViewCell cell.delegate = self // 可以根据pageAnswerStatus[indexPath.item]配置Cell的状态(比如显示已作答标记) return cell } // MARK: - UIScrollView Delegate func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { let currentOffsetX = scrollView.contentOffset.x let targetOffsetX = targetContentOffset.pointee.x let pageWidth = scrollView.bounds.width // 判断滑动方向:目标偏移大于当前偏移 → 向右滑动(下一页) if targetOffsetX > currentOffsetX { // 检查当前页是否已作答 if !pageAnswerStatus[currentPageIndex] { // 阻止滚动,强制拉回当前页位置 targetContentOffset.pointee = CGPoint(x: currentOffsetX, y: 0) // 提示用户必须完成当前页作答 showAnswerRequiredTip() } else { // 允许滚动,更新当前页码 currentPageIndex = Int((currentOffsetX + pageWidth) / pageWidth) } } else if targetOffsetX < currentOffsetX { // 向左滑动(上一页),直接允许,更新当前页码 let previousPageIndex = Int((currentOffsetX - pageWidth) / pageWidth) currentPageIndex = max(previousPageIndex, 0) // 避免页码小于0 } } // 滚动结束后确保页码准确(处理快速滚动等边界情况) func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { currentPageIndex = Int(scrollView.contentOffset.x / scrollView.bounds.width) } // MARK: - AnswerCellDelegate func userDidSubmitAnswer(in cell: AnswerCollectionViewCell) { // 获取当前Cell的索引 guard let indexPath = collectionView.indexPath(for: cell) else { return } // 更新该页的作答状态 pageAnswerStatus[indexPath.item] = true // 可以给用户一个完成提示,比如Cell上打个勾 } // 提示用户必须完成当前页作答 private func showAnswerRequiredTip() { let alert = UIAlertController(title: "温馨提示", message: "请先完成当前页面的作答哦", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "知道了", style: .default)) present(alert, animated: true) } }
关键细节说明
- 滚动拦截逻辑:通过
scrollViewWillEndDragging方法修改targetContentOffset,这是最精准的拦截方式,不会影响用户的滑动手感,只是在不允许滚动时将页面拉回原位。 - 作答状态维护:用数组
pageAnswerStatus记录每一页的作答情况,比单个布尔值更灵活,支持用户返回上一页后再次查看已作答的页面。 - 页码准确性:在
scrollViewDidEndDecelerating里更新当前页码,避免快速滚动导致的页码计算错误。 - 用户体验:拦截滚动时弹出提示,让用户明确知道不能滑动的原因。
内容的提问来源于stack exchange,提问作者Val Nolav
相关产品推荐
相关产品推荐

