如果 layoutIfNeeded() 被调用,UIView 约束不会动画

Posted

技术标签:

【中文标题】如果 layoutIfNeeded() 被调用,UIView 约束不会动画【英文标题】:UIView constraints won't animate if layoutIfNeeded() is called 【发布时间】:2017-05-02 13:23:25 【问题描述】:

我正在尝试为受限 (iconVerticalConstraint) 位于超级视图的中心 Y (+100) 的视图设置动画,特别是在动画后到达其右侧 (0) 位置。 这是我的代码:

self.view.layoutIfNeeded()

UIView.animateKeyframes(withDuration: 2, delay: 0, animations: 

    UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations:
        self.iconVerticalConstraint.constant -= 20
    )

    UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.75, animations:
        self.iconVerticalConstraint.constant -= 80
    )
)

我尝试关注this question's answer,但这并没有帮助,因为在我的情况下,我的视图包含更多子视图,它们是内在链接的,并且将layoutIfNeeded() 放在我的动画块中会为整个视图设置动画,而不仅仅是那个约束。否则这种方法根本没有动画。

【问题讨论】:

请注意,如果我将self.view.layoutIfNeeded() 移动到animateKeyframes 块内,则会发生动画,但它会为所有视图设置动画(就像我认为应该的那样),而不仅仅是约束,如第一条评论中所述。 How do I animate constraint changes?的可能重复 已编辑以解释与可能重复的差异。 为什么这被否决了?否决者能解释一下吗? 【参考方案1】:

改变约束例如

self.iconVerticalConstraint.constant -= 20

应该在动画块之外,在里面你只需调用

self.view.layoutIfNeeded() 

我想是因为你正在尝试做 2,然后打电话

self.view.layoutIfNeeded() 

它们都发生一次,考虑改为:

    self.iconVerticalConstraint.constant -= 20

    UIView.animate(withDuration: 0.25, animations: 
        self.view.layoutIfNeeded()
    )  (finished) in
     
        self.iconVerticalConstraint.constant -= 80

        UIView.animate(withDuration: 0.75, animations: 
            self.view.layoutIfNeeded()
        , completion: nil)
    

编辑:解释

当您更改约束时,视图将在下一个布局周期之前更新,通常是在您退出当前范围时。因此,当您想要为约束设置动画时,您可以在动画块之前更改常量,然后在动画块内调用 layoutIfNeeded() 这告诉它为现在强制布局循环设置动画。

现在,在您的示例中,您调用 layoutIfNeeded() 时不会执行任何操作,然后更改 2 个常量,而不告诉布局进行动画处理,因此当范围退出时,UI 将在没有动画的情况下进行布局。

在您的情况下,您希望链接 2 个常量的更改,但您无法在第一个动画完成后更改第二个常量 (80),如果您同时设置这两个常量,则两个常量都将在 FIRST 上进行调整layoutIfNeeded() 因此,动画链接仍然可以与您省略的其他动画一起使用,如果它们在第一个关键帧中,则将 then 添加到第一个动画块中,如果它们在第二个关键帧中,则添加第二个动画块。

最终编辑:

您还可以将当前应用于 self.view 的 layoutIfNeeded() 更改为实际影响它的视图,在这种情况下,iconVerticalConstraint 如果它是附加到另一个视图的视图,则说顶部附加到视图底部,如果你想要动画,你必须在它影响的每个视图上调用layoutIfNeeded()

【讨论】:

【参考方案2】:

在花了几个小时了解基本机制之后,我可以为所有遇到我情况的人提供我自己的答案。

layoutIfNeeded() 告诉视图调整其所有子视图的位置animateanimateKeyframes 或您正在使用的所有其他替代方法,而是告诉视图以您在 animate 函数中指定的方式执行 动画块 内的任何指令(默认情况下,options: .easeInOut

回到问题:问题在于animatelayoutIfNeeded异步调用,因此实际上动画中的每条指令块在 layoutIfNeeded 指令之前执行,使其无用。解决方案不是按照 SeanLintern88 的建议将 layoutIfNeeded 指令放在动画块中,因为这样我们会告诉视图根据动画选项调整其子视图的位置。这才是重点。

答案有点简单。在 subview 本身 上移动动画部分,特别是在您知道 superview 已经被“自动布局”(例如viewDidAppear)时,以便约束已定义并执行,您现在可以更改它。此时,代码应如下所示:

UIView.animateKeyframes(withDuration: 2, delay: 0, animations: 

    self.view.layoutIfNeeded()

    UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations:
        self.iconVerticalConstraint.constant -= 20
    )

    UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.75, animations:
        self.iconVerticalConstraint.constant -= 80
    )
)

因为您想要为自动布局设置动画。

另一个选项(例如,如果您没有/不想为您的子视图创建一个类)是更改子视图的 bounds/center本身在父视图的viewDidAppear 函数中。这是因为,如果您在父视图中,您知道此时自动布局已经注册,但尚未执行,更准确地说,边界和中心已经根据自动布局。

如果您随后更改它们,您将获得所需的动画(但记住:自动布局仍处于启用状态,因此您必须以反映动画最终位置的方式更改约束; 如果您愿意,可以在 animate 函数的 completion 部分中执行此操作)。

【讨论】:

以上是关于如果 layoutIfNeeded() 被调用,UIView 约束不会动画的主要内容,如果未能解决你的问题,请参考以下文章

最好在更改约束之前或之后调用 .layoutIfNeeded()

调用 layoutIfNeeded 后视图未立即显示

在 iPhone 7 上的动画块冻结 UI 中调用 layoutIfNeeded

调用 layoutIfNeeded 后 Snapkit 3.0 无法获得正确的帧

为啥在子视图中添加图层蒙版时需要调用 layoutIfNeeded()?

每次加载视图时只能调用一次 layoutIfNeeded - 例如视图加载?