Swift - 自定义 UIView 中的动画约束

Swift - Animate Constraints in Custom UIView

我正在尝试在自定义 UIView 中设置约束动画。结束布局是正确的,但我无法让 UIView.animate 正常工作,而且我看到每个人都从视图控制器调用 self.view.layoutIfNeeded(),所以这也许是我的问题。我在自定义视图本身上调用 self.layoutIfNeeded()。如果层次结构很重要,我有如下内容:ViewController > TableView > Cell > View > AnimationView.

class AnimationView: UIView {
    
    // MARK: - Properties
    let ratioMultiplier: CGFloat = 0.28
    var listLeadingConstraint: NSLayoutConstraint!
    var listTrailingConstraint: NSLayoutConstraint!
    var checkmarkLeadingConstraint: NSLayoutConstraint!
    var checkmarkTrailingConstraint: NSLayoutConstraint!
    
    // MARK: - UI Components
    lazy var listImageView: UIImageView = {
        let image = UIImage(named: "list.small")
        let result = UIImageView(image: image)
        return result
    }()
    
    lazy var checkmarkImageView: UIImageView = {
        let image = UIImage(named: "checkmark.small")
        let result = UIImageView(image: image)
        return result
    }()
    
    // MARK: - Inits
    init() {
        super.init(frame: .zero)
        setupView()
    }
    
    required init?(coder: NSCoder) {
        return nil
    }
    
    // MARK: - Methods
    func playAnimation() {
        // 1. change constraints
        listLeadingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
        listTrailingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
        checkmarkLeadingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
        checkmarkTrailingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
        self.layoutIfNeeded()
        
        // 2. animate constraints
        UIView.animate(withDuration: 3.0) {
            self.layoutIfNeeded()
        }
    }
    
    func setupView() {
        // 1. add subviews
        addSubview(listImageView)
        addSubview(checkmarkImageView)
        
        // 2. add constraints       
        ConstraintHelper.anchor(view: checkmarkImageView, options: [.top, .bottom])
        checkmarkLeadingConstraint = checkmarkImageView.leadingAnchor.constraint(equalTo: leadingAnchor)
        checkmarkTrailingConstraint = checkmarkImageView.trailingAnchor.constraint(equalTo: trailingAnchor)
        checkmarkLeadingConstraint.constant -= checkmarkImageView.frame.size.width * ratioMultiplier
        checkmarkTrailingConstraint.constant -= checkmarkImageView.frame.size.width * ratioMultiplier
        NSLayoutConstraint.activate([checkmarkLeadingConstraint, checkmarkTrailingConstraint])
        
        ConstraintHelper.anchor(view: listImageView, options: [.top, .bottom])
        listLeadingConstraint = listImageView.leadingAnchor.constraint(equalTo: leadingAnchor)
        listTrailingConstraint = listImageView.trailingAnchor.constraint(equalTo: trailingAnchor)
        NSLayoutConstraint.activate([listLeadingConstraint, listTrailingConstraint])
    }
}

其他信息:playAnimation() 是从 table 视图上的 cellForRow 调用的,我也尝试使用 self.superview?.layoutIfNeeded() 调用但没有成功。 ConstraintHelper 调用 translatesAutoresizingMaskIntoConstraints 并且约束正常工作,所以不用担心。任何帮助都会很棒。

成功了。将约束更改移动到动画块修复它,尽管这与那里的大多数帖子形成对比,后者说在动画块之前进行约束更改,准备时调用一次 layoutIfNeeded(),然后调用第二个 layoutIfNeeded()在动画块中强制更新和动画。为什么这种行为在我的代码中有所不同,我仍然不确定...

func playAnimation() {

    // update constraints & animate
    UIView.animate(withDuration: 3.0) {
        listLeadingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
        listTrailingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
        checkmarkLeadingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
        checkmarkTrailingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
        self.layoutIfNeeded()
    }
}

您的初始 post 不起作用的原因是您在约束更改后调用了您的第一个 layoutIfNeeded()。当你的动画块被执行时,根本没有任何变化可以被动画化。

改变动画块内外的约束并不重要。

常用步骤为:

  1. 在您的约束可能影响的最根视图上调用 layoutIfNeeded()。在您的情况下, self 可以,因为所有涉及的约束都是针对子视图的。在某些情况下,您可能会更改自身视图的约束,然后您需要在其父视图上调用 layoutIfNeeded
  2. 更改约束:修改常量 属性、activate/deactivate 约束等
  3. 在与第一步相同的视图上的动画块中调用 layoutIfNeeded()

请尝试以下替代方法:

func playAnimation() {
    // Request a layout to clean pending changes for the view if there's any.
    self.layoutIfNeeded()
    
    // Make your constraint changes.
    listLeadingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
    listTrailingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
    checkmarkLeadingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
    checkmarkTrailingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
    
    UIView.animate(withDuration: 3.0) {
        // Let your constraint changes update right now.
        self.layoutIfNeeded()
    }
}