UIView.animateKeyframes多关键帧动画仅首个生效其余被跳过
问题根源
两个核心问题导致动画失效:
- 动画触发时机不对:你在
viewDidLoad里调用动画时,控制器的视图还没完成Auto Layout布局,Toast的初始位置根本没计算完成,这时候提交的所有动画属性变更会被系统直接合并成最终状态,看不到过渡过程。 - 动画逻辑本身有缺陷:
- 没有为Toast停留展示阶段分配时间占比,上滑完成后立刻触发下滑
- 位移数值计算错误:你的Toast初始状态是完全隐藏在屏幕底部下方的,上滑时y方向需要向上偏移「Toast自身高度+底部安全间距」才能完全进入可视区域,你之前设置的-50偏移量不足以让视图正常显示;下滑阶段设置y:50反而会让视图向更靠下的位置移动,并非回到初始位置
- 约束激活后没有强制刷新布局,动画起始状态不准确
- 代码里存在多余的冲突约束、错误的按钮target设置,会进一步干扰布局和动画效果
修复方案
1. 调整动画触发时机
把Toast的触发逻辑从viewDidLoad移到viewDidAppear,确保开启动画前视图已经完成所有布局计算;约束激活后调用layoutIfNeeded()强制刷新布局,固定初始位置。
class ToastViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) toastTest() } func toastTest() { let toastView = ToastView(toastRenderType: "Ok", toastController: self, frame: .zero) view.addSubview(toastView) toastView.layoutOnController() // 强制刷新布局,确保动画起始位置正确 view.layoutIfNeeded() toastView.animateUp() } }
2. 修正关键帧动画逻辑
按「上滑-停留-下滑」三段分配关键帧时间占比,修正位移数值:比如总时长设为4秒,1秒上滑、2秒停留展示、1秒下滑回到初始位置。
func animateUp(){ let totalDuration: TimeInterval = 4.0 let toastHeight: CGFloat = 48 let bottomPadding: CGFloat = 20 // 上滑需要的y轴偏移量,向上为负方向 let showOffset = -(toastHeight + bottomPadding) UIView.animateKeyframes( withDuration: totalDuration, delay: 0.0, options: [.calculationModeCubic, .allowUserInteraction], animations: { // 上滑阶段:占总时长25%,从0时刻开始 UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25) { self.transform = CGAffineTransform(translationX: 0, y: showOffset) } // 停留阶段:占总时长50%,从25%进度开始,不修改属性保持展示状态 UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.5) {} // 下滑阶段:占总时长25%,从75%进度开始,重置transform回到初始位置 UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) { self.transform = .identity } } ) { completed in if completed { self.removeFromSuperview() } } }
3. 修复其他干扰布局的bug
- 移除
dismissButton初始化闭包里错误的target设置:闭包内self尚未完成初始化,且你重复添加了两次target、其中一次target指向类对象会导致点击崩溃,把target添加逻辑移到layoutToastConstraints方法末尾即可:// 删掉dismissButton初始化闭包里的addTarget代码,在layoutToastConstraints最后加: dismissButton.addTarget(self, action: #selector(clickedToast), for: .touchUpInside) - 删除重复、错误的约束:移除
dismissButton多余的leadingAnchor constant:0约束;修正label和imageView的bottom锚点,把equalTo: self.topAnchor改为equalTo: self.bottomAnchor,避免内部布局错乱。
内容的提问来源于stack exchange,提问作者kelsey-debug
相关产品推荐
相关产品推荐

