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

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

enter image description here

实现方案

要实现手势无缝切换,不能通过动态添加/移除平移手势的方式实现——这种操作会中断当前正在进行的触控序列,必须松手重按才能生效。正确的做法是让自定义平移手势和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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 18:15:03