如何通过UIPanGestureRecognizer调整contentView的高度?
通过UIPanGestureRecognizer固定底部调整contentView高度
问题背景
你的contentView已设置Auto Layout约束(trailing、leading、bottom固定,高度固定),并嵌入了子视图控制器。当前拖动时整个contentView会移动,需要实现底部固定,仅通过手势调整高度的效果。
解决方案步骤
- 关联高度约束的IBOutlet:在Storyboard中把contentView的固定高度约束拖到代码中,作为可修改的属性。
- 记录初始高度:手势开始时保存当前的高度约束值,而非视图的center。
- 动态调整高度约束:根据手势的拖动偏移量计算新高度,同时限制高度的最小/最大值。
- 手势结束的回弹处理:根据拖动速度判断是否回弹到预设的极限高度,并用动画过渡。
修改后的完整代码
import UIKit class YourViewController: UIViewController { // 连接Storyboard中contentView的高度约束 @IBOutlet weak var contentViewHeightConstraint: NSLayoutConstraint! // 子视图控制器(根据你的实际代码定义) var childViewController: UIViewController! // 可根据需求自定义最小/最大高度 private let minContentViewHeight: CGFloat = 100 private let maxContentViewHeight: CGFloat = 400 private var initialHeight: CGFloat = 0 override func viewDidLoad() { super.viewDidLoad() prepareGesture() } func prepareGesture() { let gesture = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:))) // 将手势添加到子视图控制器的view上,确保能接收拖动事件 childViewController.view.addGestureRecognizer(gesture) } @objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) { let translation = gestureRecognizer.translation(in: childViewController.view).y let velocity = gestureRecognizer.velocity(in: childViewController.view).y switch gestureRecognizer.state { case .began: gestureBegan() case .changed: gestureChanged(translation: translation) case .cancelled, .ended: gestureEnded(velocity: velocity) default: break } } func gestureBegan() { // 记录手势开始时的初始高度 initialHeight = contentViewHeightConstraint.constant } func gestureChanged(translation: CGFloat) { // 计算新高度:向上拖动(translation.y为负)增加高度,向下拖动(translation.y为正)减少高度 var newHeight = initialHeight - translation // 限制高度在最小/最大值范围内,避免超出布局边界 newHeight = max(minContentViewHeight, min(newHeight, maxContentViewHeight)) // 更新高度约束 contentViewHeightConstraint.constant = newHeight // 实时刷新布局 view.layoutIfNeeded() } func gestureEnded(velocity: CGFloat) { var targetHeight = contentViewHeightConstraint.constant // 根据拖动速度判断回弹逻辑 if velocity < -300 { // 快速向上拖,自动展开到最大高度 targetHeight = maxContentViewHeight } else if velocity > 300 { // 快速向下拖,自动收缩到最小高度 targetHeight = minContentViewHeight } else { // 慢速拖动时,根据当前高度自动贴近最近的极限值 let midHeight = (minContentViewHeight + maxContentViewHeight) / 2 targetHeight = targetHeight > midHeight ? maxContentViewHeight : minContentViewHeight } // 动画过渡到目标高度 UIView.animate(withDuration: 0.3) { self.contentViewHeightConstraint.constant = targetHeight self.view.layoutIfNeeded() } } }
关键说明
- 约束优先:始终通过修改Auto Layout约束的
constant值来调整高度,这样能保证bottom约束持续生效,contentView底部固定不动。 - 坐标系映射:iOS中y轴向下为正,所以向上拖动时
translation.y为负数,用initialHeight - translation计算新高度,符合“上拖增高、下拖减矮”的直觉。 - 边界限制:必须设置最小/最大高度,防止contentView高度异常导致布局错乱。
- 动画生效:约束更新后要在动画闭包中调用
layoutIfNeeded(),才能让高度变化以动画形式呈现。
内容的提问来源于stack exchange,提问作者mb0
相关产品推荐
相关产品推荐

