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

UIView Transform动画未按时长执行:iOS动画瞬间完成问题求助

解决Action Sheet风格弹出动画失效的问题

Hey,我一眼就看出问题所在了!你遇到的核心问题是在viewDidLoad阶段,Auto Layout还没完成布局计算,导致你设置的初始位移完全无效,动画自然看起来是瞬间完成的。

问题根源拆解

在viewDidLoad里初始化containerView时,你写了:

view.transform = .init(translationX: 0, y: view.frame.height)

但此时containerView的frame还是CGRect.zero(因为你把translatesAutoresizingMaskIntoConstraints设为了false,Auto Layout还没来得及计算它的实际尺寸),所以view.frame.height的值是0——相当于你给containerView设置了一个“无偏移”的初始状态,后续动画从“无偏移”到identity(还是无偏移),自然看不到任何过渡效果。

两种靠谱的解决方案

方案一:把动画移到viewDidAppear阶段(简单直接)

viewDidAppear方法调用时,Auto Layout已经完成了所有布局计算,containerView的实际尺寸已经确定。我们只需要在视图即将显示时设置初始偏移,然后在视图显示后执行动画:

final class CustomCardViewController: UIViewController { 
    // ... 你的其他属性 ...
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // 此时containerView的frame已经有正确高度了
        containerView.transform = .init(translationX: 0, y: containerView.frame.height)
    }

    override func viewDidLoad() { 
        super.viewDidLoad() 
        backgroundMask.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTapToDismiss))) 
        modalPresentationStyle = .overFullScreen 
        [backgroundMask, containerView].forEach(view.addSubview(_:)) 
        NSLayoutConstraint.activate([ 
            backgroundMask.topAnchor.constraint(equalTo: view.topAnchor), 
            backgroundMask.leadingAnchor.constraint(equalTo: view.leadingAnchor), 
            backgroundMask.bottomAnchor.constraint(equalTo: view.bottomAnchor), 
            backgroundMask.trailingAnchor.constraint(equalTo: view.trailingAnchor), 
            containerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 24), 
            containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -24), 
            containerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -24), 
            containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 200) 
        ]) 
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // 执行滑入动画
        UIView.animate(withDuration: 5, delay: 0.33, options: .curveEaseOut, animations: { 
            self.containerView.transform = .identity 
        }, completion: nil) 
    }

    // ... 你的其他方法 ...
}

方案二:用约束动画实现(更符合Auto Layout最佳实践)

如果想严格遵循Auto Layout的规则,我们可以通过修改约束的方式实现动画,而不是直接操作transform:

final class CustomCardViewController: UIViewController { 
    private let backgroundMask: UIView = { 
        let view = UIView(frame: .zero) 
        view.translatesAutoresizingMaskIntoConstraints = false 
        view.backgroundColor = .init(white: 0, alpha: 0.3) 
        return view 
    }() 
    private lazy var containerView: UIView = { 
        let view = UIView(frame: .zero) 
        view.translatesAutoresizingMaskIntoConstraints = false 
        view.backgroundColor = .red 
        return view 
    }()
    // 把bottom约束存为变量,方便后续修改
    private var containerBottomConstraint: NSLayoutConstraint!

    override func viewDidLoad() { 
        super.viewDidLoad() 
        backgroundMask.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTapToDismiss))) 
        modalPresentationStyle = .overFullScreen 
        [backgroundMask, containerView].forEach(view.addSubview(_:)) 

        // 初始化bottom约束,让containerView完全在屏幕下方
        containerBottomConstraint = containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 200 + 24) // 200是最小高度,24是原来的底部间距

        NSLayoutConstraint.activate([ 
            backgroundMask.topAnchor.constraint(equalTo: view.topAnchor), 
            backgroundMask.leadingAnchor.constraint(equalTo: view.leadingAnchor), 
            backgroundMask.bottomAnchor.constraint(equalTo: view.bottomAnchor), 
            backgroundMask.trailingAnchor.constraint(equalTo: view.trailingAnchor), 
            containerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 24), 
            containerBottomConstraint,
            containerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -24), 
            containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 200) 
        ]) 

        // 先强制布局生效,确保初始位置正确
        view.layoutIfNeeded()

        // 动画修改约束,实现滑入效果
        UIView.animate(withDuration: 5, delay: 0.33, options: .curveEaseOut, animations: { 
            self.containerBottomConstraint.constant = -24
            self.view.layoutIfNeeded() // 触发约束动画
        }, completion: nil) 
    } 

    // ... 你的其他方法 ...
}

这种方式的好处是完全基于Auto Layout,不会出现transform和约束冲突的问题,适配不同屏幕也更可靠。

内容的提问来源于stack exchange,提问作者Harry Blue

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 15:08:11