读数:
从这个回答
这是公认的答案建议的动画您的观点改变:
_addBannerDistanceFromBottomConstraint.constant = 0
UIView.animate(withDuration: 5) {
self.view.layoutIfNeeded()
}
为什么我们要调用layoutIfNeeded
,而并不是在改变帧。我们正在更改约束,所以(根据这个其他答案)我们不应该调用setNeedsUpdateConstraints
吗?
类似地,这个备受关注的回答说:
如果后面发生了一些更改,使其中一个约束无效,则应立即删除该约束并调用setNeedsUpdateConstraints。
意见:
我确实试过把它们都用上。使用setNeedsLayout
,我的视图正确地向左动画
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func animate(_ sender: UIButton) {
UIView.animate(withDuration: 1.8, animations: {
self.centerXConstraint.isActive = !self.centerXConstraint.isActive
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
})
}
@IBOutlet weak var centerYConstraint: NSLayoutConstraint!
@IBOutlet var centerXConstraint: NSLayoutConstraint!
}
然而,使用setNeedsUpdateConstraints
并不是动画,它只是快速地将视图移动到左边的。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func animate(_ sender: UIButton) {
UIView.animate(withDuration: 1.8, animations: {
self.centerXConstraint.isActive = !self.centerXConstraint.isActive
self.view.setNeedsUpdateConstraints()
self.view.updateConstraintsIfNeeded()
})
}
@IBOutlet weak var centerYConstraint: NSLayoutConstraint!
@IBOutlet var centerXConstraint: NSLayoutConstraint!
}
如果我不想要动画,那么使用view.setNeedsLayout
或view.setNeedsUpdateConstraints
,把它移到左边。然而:
view.setNeedsLayout
,在我的按钮被点击后,我的viewDidLayoutSubviews
断点就到达了。但是从未到达updateViewConstraints
断点。这让我很困惑约束是如何更新的.view.setNeedsUpdateConstraints
时,点击按钮后,updateViewConstraints
断点就到达,然后到达viewDidLayoutSubviews
断点。这是有意义的,约束被更新,然后调用layoutSubviews。问题:
根据我的阅读资料:如果你改变了约束条件,那么你必须调用setNeedsUpdateConstraints
,但根据我的观察,这是错误的。有以下代码就足够动画了:
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
为什么?
然后我想,也许在头罩下,它是通过其他方式更新约束的。所以我在override func updateViewConstraints
和override func viewDidLayoutSubviews
上放置了一个断点,但是只有viewDidLayoutSubviews
到达了它的断点。
那么,自动布局引擎是如何管理这个的呢?
发布于 2017-12-26 16:21:29
这是iOS开发人员之间常见的误解。
这里是我对自动布局的“黄金规则”之一:
不要为“更新约束”而烦恼。
您无需调用这些方法中的任何一个:
setNeedsUpdateConstraints()
updateConstraintsIfNeeded()
updateConstraints()
updateViewConstraints()
除了非常罕见的情况,您有一个非常复杂的布局,减缓了您的应用程序(或您故意选择实现布局变化的非典型方式)。
更改布局的首选方法
通常,当您想更改布局时,您会在按钮点击或任何事件触发更改后直接激活/停用或更改布局约束,例如在按钮的操作方法中:
@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
toggleLayout()
}
func toggleLayout() {
isCenteredLayout = !isCenteredLayout
if isCenteredLayout {
centerXConstraint.isActive = true
} else {
centerXConstraint.isActive = false
}
}
就像苹果在他们的自动布局指南里说的那样
在发生影响更改后立即更新约束几乎总是更干净、更容易。将这些更改推迟到后面的方法会使代码更复杂,更难以理解。
当然,您也可以在动画中包装此约束更改:首先执行约束更改,然后通过在动画闭包中调用layoutIfNeeded()
来动画化更改:
@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
// 1. Perform constraint changes:
toggleLayout()
// 2. Animate the changes:
UIView.animate(withDuration: 1.8, animations: {
view.layoutIfNeeded()
}
}
无论何时更改约束,系统都会自动安排延迟的布局传递,这意味着系统将在不久的将来重新计算布局。不需要调用setNeedsUpdateConstraints()
,因为您只是自己更新(更改)了约束!需要更新的是布局,即所有视图的框架,而不是任何其他约束。
失效原理
如前所述,iOS布局系统通常不会立即对约束更改作出反应,而只是安排延迟的布局传递。那是因为表演的原因。你可以这样想:
当你去买食品杂货时,你会把一件东西放在你的购物车里,但你不会马上付款。相反,你把其他的东西放在你的购物车里,直到你觉得你得到了你所需要的一切。只有到那时,你才去收银台,立刻把所有的杂货都付了。效率更高。
由于这种延迟的布局传递,需要一种特殊的机制来处理布局更改。我称之为无效原则。这是一个两步的机制:
就布局引擎而言,这对应于:
setNeedsLayout()
layoutIfNeeded()
和
setNeedsUpdateConstraints()
updateConstraintsIfNeeded()
第一对将导致立即(而不是延迟)的布局传递:首先使布局失效,然后在布局无效时立即重新计算布局(当然是这样)。
通常,您不需要考虑布局传递是现在发生还是几毫秒后发生,所以通常只调用setNeedsLayout()
来使布局无效,然后等待延迟的布局传递。这使您有机会对约束执行其他更改,然后稍加更新布局(→购物车)。
您只需要在需要立即重新计算布局时调用layoutIfNeeded()
。当您需要根据新布局的结果框架执行其他计算时,情况可能是这样的。
第二对方法将导致立即调用updateConstraints()
(在视图上或在视图控制器上的updateViewConstraints()
)。但这是你通常不该做的事。
在批处理中更改布局
只有当您的布局非常缓慢,并且您的UI由于布局更改而感到滞后时,您才可以选择与上面所述的方法不同的方法:与其直接响应按钮点击更新约束,您只需“注意”您想要更改的内容,以及另一个“注意”您的约束需要更新。
@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
// 1. Make a note how you want your layout to change:
isCenteredLayout = !isCenteredLayout
// 2. Make a note that your constraints need to be updated (invalidate constraints):
setNeedsUpdateConstraints()
}
这将安排延迟的布局传递,并确保在布局传递期间调用updateConstraints()
/ updateViewConstraints()
。因此,您现在甚至可以执行其他更改,并调用setNeedsUpdateConstraints()
一千次,在下一次布局传递期间,- your约束仍然只更新一次。
现在您重写updateConstraints()
/ updateViewConstraints()
,并根据当前布局状态执行必要的约束更改(即您在“1”中“注意到”的内容):
override func updateConstraints() {
if isCenteredLayout {
centerXConstraint.isActive = true
} else {
centerXConstraint.isActive = false
}
super.updateConstraints()
}
同样,如果布局非常慢,并且您正在处理的是数百或数千个约束,这只是您最后的选择。到目前为止,我还没有必要在任何项目中使用updateConstraints()
。
我希望这能让事情更清楚些。
追加资源:
发布于 2017-12-14 23:45:33
setNeedsUpdateConstraints
将根据您所做的更改更新将要更改的约束。例如,如果您的视图有一个相邻的视图,其中有一个水平距离的约束,并且该近邻视图被移除,则该约束现在无效。在这种情况下,您应该删除该约束并调用setNeedsUpdateConstraints
。它基本上确保所有约束都是有效的。这不会重新绘制视图。您可以阅读更多关于它的这里。
另一方面,setNeedsLayout
标记用于重绘的视图,并将其放入动画块中,使绘图成为动画。
https://stackoverflow.com/questions/47823639
复制相似问题