小编典典

为什么约束更改或动画不需要调用setNeedsUpdateConstraints?

swift

读物:

从这个答案

这是被接受的答案建议为您的视图变化设置动画:

_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.setNeedsLayoutview.setNeedsUpdateConstraints将其向左移动。然而:

  • 使用view.setNeedsLayout,点击我的按钮后,viewDidLayoutSubviews到达断点。但是updateViewConstraints从未达到断点。这使我对约束的更新方式感到困惑。
  • 使用view.setNeedsUpdateConstraints,在点击按钮后updateViewConstraints到达我的 断点, 然后 到达viewDidLayoutSubviews断点。这确实是有道理的,更新约束,然后调用layoutSubviews。

问题:

根据我的读物:如果您更改约束条件,然后使其生效,则必须致电setNeedsUpdateConstraints,但是根据我的观察,这是错误的。拥有以下代码足以制作动画:

self.view.setNeedsLayout()
self.view.layoutIfNeeded()

为什么?

然后我想也许是在幕后通过其他方式更新了约束。所以,我把一个断点override func updateViewConstraintsoverridefuncviewDidLayoutSubviews,但只viewDidLayoutSubviews达到了断点。

那么,自动版式引擎如何管理呢?


阅读 334

收藏
2020-07-07

共1个答案

小编典典

这是iOS开发人员中常见的误解。

这是我的“自动版式”的“黄金法则”之一:

不要为 “更新约束” 而烦恼。

永远 需要调用任何这些方法:

  • setNeedsUpdateConstraints()
  • updateConstraintsIfNeeded()
  • updateConstraints()
  • updateViewConstraints()

除了 极少数情况下,您的布局非常复杂会减慢您的应用程序运行速度(或者您故意选择以非典型方式实施布局更改)。

更改布局的首选方法

通常,当您想更改布局时,您可以在点击按钮或发生任何事件触发更改后立即激活/停用或更改布局约束,例如,在按钮的操作方法中:

@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
    toggleLayout()
}

func toggleLayout() {
    isCenteredLayout = !isCenteredLayout

    if isCenteredLayout {
        centerXConstraint.isActive = true 
    } else {
        centerXConstraint.isActive = false
    }
}

正如Apple在其《自动布局指南》中所说:

发生影响性更改之后,几乎总是更干净,更轻松地立即更新约束。将这些更改推迟到以后的方法中会使代码更加复杂且难以理解。

当然,您也可以在动画中包装此约束更改:首先执行约束更改,然后通过调用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布局系统通常不会立即对约束更改做出反应,而只会安排延迟的布局通过。这是出于性能原因。这样想:

当您去购物时,您将商品放入购物车,但没有立即付款。取而代之的是,您将其他物品放到购物车中,直到感觉得到所需的一切为止。只有这样,您才能前往收银员并立即支付所有杂货。这样更有效。

由于这种延迟的布局传递,需要一种特殊的机制来处理布局更改。我称之为 无效原则 。这是一个两步机制:

  1. 您将某些内容标记为无效。
  2. 如果无效,请执行一些操作以使其再次有效。

就布局引擎而言,它对应于:

  1. setNeedsLayout()
  2. layoutIfNeeded()

  1. setNeedsUpdateConstraints()
  2. 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()一千次-
您的约束在下一次布局遍历中仍只会更新 一次

现在,您覆盖updateConstraints()/updateViewConstraints()并根据您当前的布局状态(即您在上面的“1”中“注意到”的内容)执行必要的约束更改:

override func updateConstraints() {
    if isCenteredLayout {
        centerXConstraint.isActive = true 
    } else {
        centerXConstraint.isActive = false
    }

    super.updateConstraints()
}

同样,如果布局真的很慢并且您要处理数百或数千个约束,那么这只是您的最后选择。我 从未updateConstraints()在任何项目中使用过。

我希望这可以使事情变得更清晰。

其他资源:

2020-07-07