如果 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()
告诉视图调整其所有子视图的位置。
animate
或 animateKeyframes
或您正在使用的所有其他替代方法,而是告诉视图以您在 animate
函数中指定的方式执行 动画块 内的任何指令(默认情况下,options: .easeInOut
)
回到问题:问题在于animate
和layoutIfNeeded
被异步调用,因此实际上动画中的每条指令块在 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()
在 iPhone 7 上的动画块冻结 UI 中调用 layoutIfNeeded
调用 layoutIfNeeded 后 Snapkit 3.0 无法获得正确的帧