解决UIViewPropertyAnimator添加UIPan手势速度后动画卡顿问题
修复UIViewPropertyAnimator滑动后突然减速的问题
我之前也碰到过一模一样的情况,这种“撞墙式”的减速感,本质是手势速度没有正确映射到动画的进度空间,导致弹簧参数的初始速度和动画实际运动不匹配。咱们一步步来修复:
问题根源分析
你当前的速度计算逻辑没有做「归一化处理」:手势返回的velocity(in:)是屏幕像素级的速度,但UISpringTimingParameters需要的是相对于动画总位移的进度速度(比如总位移是300pt,那么300pt/s的速度对应归一化速度1,代表每秒完成100%的动画进度)。直接用像素速度会让弹簧系统误判运动幅度,触发过度阻尼,导致突然减速。
具体修复步骤
1. 修正速度归一化逻辑
替换你现有的velocity()方法,把像素速度转换成动画进度维度的速度:
func normalizedPanVelocity() -> CGVector { guard let animator = runningAnimators.first else { return .zero } let panVelocity = panRecognizer.velocity(in: view) // 这里替换成你的popup菜单的总滑动位移(比如从底部到完全展开的距离) let totalAnimationDistance: CGFloat = popupOffset // 将像素速度转换为「动画进度/秒」的归一化速度 let normalizedY = panVelocity.y / totalAnimationDistance // 如果你是水平滑动,改用panVelocity.x即可 return CGVector(dx: 0, dy: normalizedY) }
2. 优化动画继续逻辑
在手势结束时,先确保动画状态连贯,再用正确的弹簧参数继续动画:
case .ended: let normalizedVelocity = normalizedPanVelocity() // 调整弹簧参数,模拟控制中心的轻盈手感 let springParams = UISpringTimingParameters( mass: 1.2, stiffness: 180.0, damping: 25.0, initialVelocity: normalizedVelocity ) runningAnimators.forEach { animator in // 先暂停当前动画,保证状态一致 if animator.isRunning { animator.pauseAnimation() } // durationFactor设为0,让系统自动计算剩余动画时长 animator.continueAnimation(withTimingParameters: springParams, durationFactor: 0) }
3. 手势拖动时的额外优化
确保拖动过程中是通过修改fractionComplete来驱动动画,而不是直接修改frame,这样动画状态才会连贯:
case .changed: let translation = recognizer.translation(in: view) // 计算当前进度,限制在0-1之间 let currentProgress = min(max(0, runningAnimators[0].fractionComplete - translation.y / popupOffset), 1) runningAnimators[0].fractionComplete = currentProgress // 重置手势位移,避免累积误差 recognizer.setTranslation(.zero, in: view)
为什么这样有效?
- 归一化速度让弹簧系统能正确理解手势的运动幅度,不会出现“速度过载”导致的突然阻尼。
- 调整后的弹簧参数更贴近系统控制中心的手感:mass不要过大,stiffness保证动画响应及时,damping让动画快速稳定不弹跳。
内容的提问来源于stack exchange,提问作者Hashem Aboonajmi
相关产品推荐
相关产品推荐

