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

iOS UIView动画completion块提前执行 移除视图动画异常排查

问题根因

completion回调提前触发是两个问题共同导致的:

  • 动画重复触发打断:UIPanGestureRecognizer的.changed回调会随着手指移动每帧执行,只要视图位置满足边缘判断条件,就会重复调用animatedHideSoundView。新创建的动画会直接打断上一个未完成的同视图动画,被打断的动画会立刻执行completion回调。
  • 动画属性冲突:拖拽跟手、手势结束的归位逻辑都是直接修改视图的center属性,而隐藏动画修改的是transform属性,两个属性同时控制视图位置,且手势回调中持续修改center会覆盖动画的状态,系统判定动画无法正常走完流程,就会立刻结束动画触发回调。
修复方案

从三个点调整逻辑,彻底避免动画被打断:

  1. 增加动画运行标记,防止重复触发隐藏动画
  2. 统一所有位置操作的属性,全部使用center控制视图位置,避免transform和center冲突
  3. 隐藏动画执行期间禁用手势,避免手势回调持续修改视图位置打断动画

具体实现代码

首先在控制器中新增状态标记:

// 记录隐藏动画是否正在运行,避免重复触发
private var isHideAnimationRunning = false

修正隐藏动画方法:

private func animatedHideSoundView(toRight: Bool) {
    // 动画正在运行时直接返回,不重复创建动画
    guard !isHideAnimationRunning else { return }
    isHideAnimationRunning = true
    
    // 统一使用center计算滑出屏幕的目标位置,和拖拽逻辑保持一致
    let targetCenterX = toRight ? (screenWidth + soundView.bounds.width / 2) : (-soundView.bounds.width / 2)
    let targetCenter = CGPoint(x: targetCenterX, y: soundView.center.y)
    
    // 动画执行期间禁用平移手势,避免手势更新打断动画
    soundView.gestureRecognizers?.forEach { $0.isEnabled = false }
    
    UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut) {
        self.soundView.center = targetCenter
    } completion: { [weak self] isFinished in
        guard let self = self else { return }
        defer {
            // 无论动画是否完成,恢复手势可用状态、重置标记
            self.soundView.gestureRecognizers?.forEach { $0.isEnabled = true }
            self.isHideAnimationRunning = false
        }
        
        // 只有动画正常走完才执行移除和暂停逻辑
        guard isFinished else { return }
        self.soundView.removeFromSuperview()
        self.songPlayer.pause()
    }
}

调整手势回调的.changed分支,增加动画运行状态拦截:

case .changed:
    // 隐藏动画运行中不处理位置更新,避免打断动画
    guard !isHideAnimationRunning else { break }
    soundView.center = CGPoint(x: touchPoint.x - initialOffset.x, y: touchPoint.y - initialOffset.y)
    if notHiddenSoundViewRect.minX > soundView.frame.minX {
        animatedHideSoundView(toRight: false)
    } else if notHiddenSoundViewRect.maxX < soundView.frame.maxX {
        animatedHideSoundView(toRight: true)
    }
固定延迟方案的缺陷

DispatchQueue.main.asyncAfter的固定延迟逻辑和动画实际执行状态完全解耦,存在明显隐患:

  • 如果动画因为系统负载高、前序动画阻塞出现执行延迟,固定延迟会导致视图还没滑出屏幕就被移除,动画依然突兀
  • 如果后续调整动画时长,很容易忘记同步修改延迟时间,导致逻辑和动画不匹配
  • 如果动画被意外打断,延迟块依然会执行移除视图、暂停播放的逻辑,会出现不符合预期的业务错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:21:18