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

UIView.animateKeyframes多关键帧动画仅首个生效其余被跳过

问题根源

两个核心问题导致动画失效:

  1. 动画触发时机不对:你在viewDidLoad里调用动画时,控制器的视图还没完成Auto Layout布局,Toast的初始位置根本没计算完成,这时候提交的所有动画属性变更会被系统直接合并成最终状态,看不到过渡过程。
  2. 动画逻辑本身有缺陷:
    • 没有为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 14:18:18