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) }
问题根源分析
之前的代码有两个关键问题:
- 用
stopAnimation(true)停止动画时,会直接把SheetView跳到动画的目标位置(也就是父视图中心),而不是停在当前屏幕显示的位置,导致拖拽起始点完全不符合预期 - 每次手势开始时都往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
相关产品推荐
相关产品推荐

