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

iOS带PanGesture的可交互动画问题:SheetView动画时无法拖拽

解决SheetView动画中无法响应拖拽的问题

我最近碰到个头疼的问题:我的SheetView初始顶部和父视图中心对齐,支持垂直拖拽,手势结束后会自动动画回到父视图中心。但只要SheetView处于回中心的动画过程中,我就没法直接拖拽它改变位置——必须等当前动画完全跑完,才能响应下一次拖拽操作。下面是我目前的代码:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    sheetView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
    middlePoint = CGPoint(x: view.bounds.midX, y: view.bounds.height)
    sheetView.center = middlePoint
}

@IBAction func handlePan(_ sender: UIPanGestureRecognizer) {
    let recognizer = sender
    let translation = recognizer.translation(in: self.view)
    
    if recognizer.state == .began {
        if animator.isRunning {
            animator.stopAnimation(true)
        }
        animator.addAnimations {
            self.sheetView.center = self.middlePoint
        }
        animator.pauseAnimation()
    } else if recognizer.state == .changed {
        self.sheetView.center.y += translation.y
    } else if recognizer.state == .ended || recognizer.state == .cancelled {
        if animator.isRunning {
            animator.stopAnimation(true)
        }
        animator.addAnimations {
            self.sheetView.center = self.middlePoint
        }
        animator.startAnimation()
    }
    recognizer.setTranslation(CGPoint.zero, in: self.view)
}

问题根源分析

之前的代码有两个关键问题:

  1. 用stopAnimation(true)停止动画时,会直接把SheetView跳到动画的目标位置(也就是父视图中心),而不是停在当前屏幕显示的位置,导致拖拽起始点完全不符合预期
  2. 每次手势开始时都往animator里添加新动画,容易造成动画状态混乱,没法正确响应中途的拖拽操作

修正后的代码

下面是调整后的实现,能完美解决中途拖拽的问题:

// 先在类里声明animator,并在viewDidLoad初始化
var animator: UIViewPropertyAnimator!

override func viewDidLoad() {
    super.viewDidLoad()
    // 初始化一个基础的动画器,后续按需重置
    animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut)
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    sheetView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
    middlePoint = CGPoint(x: view.bounds.midX, y: view.bounds.height)
    sheetView.center = middlePoint
}

@IBAction func handlePan(_ sender: UIPanGestureRecognizer) {
    let translation = sender.translation(in: self.view)
    
    switch sender.state {
    case .began:
        // 如果动画正在运行,先停止并获取当前屏幕上的实际位置
        if animator.isRunning {
            // 停止动画但不跳到结束,保留当前进度
            animator.stopAnimation(false)
            // 通过presentationLayer拿到用户看到的实时位置(动画过程中model层位置未更新)
            if let currentCenter = sheetView.layer.presentation()?.position {
                sheetView.center = currentCenter
            }
            // 结束当前动画状态,清理动画器
            animator.finishAnimation(at: .current)
        }
    case .changed:
        // 实时更新SheetView的位置
        sheetView.center.y += translation.y
    case .ended, .cancelled:
        // 重置动画器,添加回到中心的动画并启动
        animator.stopAnimation(true)
        animator.finishAnimation(at: .current)
        animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) {
            self.sheetView.center = self.middlePoint
        }
        animator.startAnimation()
    default:
        break
    }
    
    sender.setTranslation(.zero, in: self.view)
}

关键改进点

  • 动画器初始化与重置:在viewDidLoad里初始化动画器,手势结束时重新创建,避免动画状态堆积导致的混乱
  • 中途打断动画的正确姿势:用stopAnimation(false)停止动画,保留当前进度,再通过presentationLayer获取屏幕上的实时位置,确保拖拽从用户看到的真实位置开始
  • 清理动画状态:每次处理手势前都正确结束上一次的动画状态,让动画器始终处于可正常响应的状态

这样修改后,你就能在SheetView回中心的动画过程中,直接拖拽它改变位置,无需等待动画完成啦!

内容的提问来源于stack exchange,提问作者FE_Tech

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:40:13