如何实现UITableView配合PanGesture无缝切换全屏与内容滚动效果

实现方案
要实现手势无缝切换,不能通过动态添加/移除平移手势的方式实现——这种操作会中断当前正在进行的触控序列,必须松手重按才能生效。正确的做法是让自定义平移手势和UITableView自带的滚动手势共存,通过状态判断控制两个手势的响应优先级,在同一次拖动过程中切换响应逻辑。
修改后的完整代码如下:
import UIKit class ViewController: UIViewController, UIGestureRecognizerDelegate { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var bottomViewTopConstraint: NSLayoutConstraint! @IBOutlet weak var bottomView: UIView! // 底部视图最小top约束(全屏状态) lazy var minTopConstant = (UIScreen.main.bounds.height * 0.6) * -1 // 底部视图最大top约束(最小化状态) let maxTopConstant: CGFloat = -24 var panGesture: UIPanGestureRecognizer! // 标记当前是否正在拖动底部视图 var isDraggingBottomView = false override func viewDidLoad() { super.viewDidLoad() panGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:))) // 设置手势代理 panGesture.delegate = self bottomView.isUserInteractionEnabled = true tableView.addGestureRecognizer(panGesture) tableView.delegate = self tableView.dataSource = self // 取消tableView自动调整的内边距,避免偏移判断出错 tableView.contentInsetAdjustmentBehavior = .never } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() bottomView.clipsToBounds = true bottomView.layer.cornerRadius = 20 bottomView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] } // 允许两个手势同时识别 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } @objc func draggedView(_ sender:UIPanGestureRecognizer){ self.view.bringSubviewToFront(bottomView) let translation = sender.translation(in: self.view) let velocity = sender.velocity(in: self.view) switch sender.state { case .began: // 开始拖动时判断是否需要响应底部视图拖动:底部没全屏 或者 已经全屏但tableView滚到了顶部且向下拉 isDraggingBottomView = bottomViewTopConstraint.constant > minTopConstant || (tableView.contentOffset.y <= 0 && velocity.y > 0) case .changed: guard isDraggingBottomView else { return } // 调整底部视图约束 var newConstant = bottomViewTopConstraint.constant + translation.y // 限制约束范围 newConstant = max(min(newConstant, maxTopConstant), minTopConstant) bottomViewTopConstraint.constant = newConstant // 拖动底部视图时固定tableView的偏移,不让它滚动 tableView.contentOffset = .zero sender.setTranslation(.zero, in: self.view) view.layoutIfNeeded() case .ended, .cancelled: isDraggingBottomView = false default: break } } } extension ViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 100 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .default, reuseIdentifier: "cell") cell.textLabel?.text = "Row \(indexPath.row)" return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print ("Cell selected \(indexPath.row)") } }
逻辑说明
- 核心通过
UIGestureRecognizerDelegate的共存协议让两个手势同时响应,无需增删手势 - 拖动开始时先判断当前状态,决定本次手势是控制底部视图位移还是控制列表滚动
- 控制底部视图位移时固定列表的
contentOffset为0,避免列表同时滚动出现冲突 - 列表滚动到底部/顶部时自动切换响应逻辑,全程无需松开手指即可完成交互切换
内容的提问来源于stack exchange,提问作者Muhammad Usman
相关产品推荐
相关产品推荐

