CATransaction完成块在新增子视图动画时立即触发问题排查
你遇到的这个问题,核心原因是刚调用addSubview(imageView)后立刻给视图的layer添加动画并提交CATransaction,但此时imageView还没完成首次布局和渲染——它的layer还没真正加入屏幕的渲染树中。
系统的CATransaction完成块是用来监听事务内所有动画执行状态的,但如果动画被添加到一个尚未活跃(还未被渲染过)的layer上,系统会判定这些动画没有实际启动执行,直接触发完成块,导致它被立即调用。
另外还有个潜在细节:如果你的beginTime参数是直接基于CACurrentMediaTime()设置(未加延迟),而layer还没启动自己的时间线,这个beginTime对layer来说相当于“过去的时间”,也会让动画直接进入完成状态,进一步触发完成块。
要解决这个问题,你需要确保imageView的layer已加入渲染树、完成首次布局后,再添加动画并提交事务。最直接的做法是把动画逻辑放到下一个RunLoop周期执行,用DispatchQueue.main.async包裹:
addSubview(imageView) // 把动画逻辑延后到下一个RunLoop,确保视图完成布局渲染 DispatchQueue.main.async { CATransaction.begin() let posAnimation = self.positionAnimation(startPoint: startPoint, endPoint: endPoint, beginTime: beginTime, duration: duration) let alpAnimation = self.alphaAnimation(beginTime: beginTime, duration: duration) CATransaction.setCompletionBlock{ [weak self] in print("deleting view") imageView.removeFromSuperview() } imageView.layer.add(posAnimation, forKey: nil) imageView.layer.add(alpAnimation, forKey: nil) CATransaction.commit() }
另外,如果你设置的beginTime是固定值而非基于当前时间的延迟,建议改成基于layer自身时间线来设置,避免时间不匹配:
// 改用相对于layer当前时间的延迟来设置beginTime positionAnimation.beginTime = imageView.layer.convertTime(CACurrentMediaTime(), from: nil) + delayTime
还有个小细节:你当前注释掉了imageView.removeFromSuperview(),修复完成块问题后记得取消注释。不过要注意,因为你设置了isRemovedOnCompletion = false和fillMode = .forwards,动画结束后layer会保持最终状态,此时移除视图没问题,但如果之后要复用这个视图,记得清除layer上的动画,避免残留状态影响后续使用。
内容的提问来源于stack exchange,提问作者Ondrej Synak

