首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么不需要调用setNeedsUpdateConstraints来进行约束更改或动画?

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

Stack Overflow用户
提问于 2017-12-14 23:24:31
回答 2查看 7.7K关注 0票数 22

读数:

从这个回答

这是公认的答案建议的动画您的观点改变:

代码语言:javascript
运行
复制
_addBannerDistanceFromBottomConstraint.constant = 0

UIView.animate(withDuration: 5) {
    self.view.layoutIfNeeded()
}

为什么我们要调用layoutIfNeeded,而并不是在改变帧。我们正在更改约束,所以(根据这个其他答案)我们不应该调用setNeedsUpdateConstraints吗?

类似地,这个备受关注的回答说:

如果后面发生了一些更改,使其中一个约束无效,则应立即删除该约束并调用setNeedsUpdateConstraints。

意见:

我确实试过把它们都用上。使用setNeedsLayout,我的视图正确地向左动画

代码语言:javascript
运行
复制
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 并不是动画,它只是快速地将视图移动到左边的

代码语言:javascript
运行
复制
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,但根据我的观察,这是错误的。有以下代码就足够动画了:

代码语言:javascript
运行
复制
self.view.setNeedsLayout()
self.view.layoutIfNeeded()

为什么?

然后我想,也许在头罩下,它是通过其他方式更新约束的。所以我在override func updateViewConstraintsoverride func viewDidLayoutSubviews上放置了一个断点,但是只有viewDidLayoutSubviews到达了它的断点。

那么,自动布局引擎是如何管理这个的呢?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-12-26 16:21:29

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

这里是我对自动布局的“黄金规则”之一:

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

您无需调用这些方法中的任何一个:

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

除了非常罕见的情况,您有一个非常复杂的布局,减缓了您的应用程序(或您故意选择实现布局变化的非典型方式)。

更改布局的首选方法

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

代码语言:javascript
运行
复制
@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
    toggleLayout()
}

func toggleLayout() {
    isCenteredLayout = !isCenteredLayout

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

就像苹果在他们的自动布局指南里说的那样

在发生影响更改后立即更新约束几乎总是更干净、更容易。将这些更改推迟到后面的方法会使代码更复杂,更难以理解。

当然,您也可以在动画中包装此约束更改:首先执行约束更改,然后通过在动画闭包中调用layoutIfNeeded()来动画化更改:

代码语言:javascript
运行
复制
@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由于布局更改而感到滞后时,您才可以选择与上面所述的方法不同的方法:与其直接响应按钮点击更新约束,您只需“注意”您想要更改的内容,以及另一个“注意”您的约束需要更新。

代码语言:javascript
运行
复制
@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”中“注意到”的内容):

代码语言:javascript
运行
复制
override func updateConstraints() {
    if isCenteredLayout {
        centerXConstraint.isActive = true 
    } else {
        centerXConstraint.isActive = false
    }

    super.updateConstraints()
}

同样,如果布局非常慢,并且您正在处理的是数百或数千个约束,这只是您最后的选择。到目前为止,我还没有必要在任何项目中使用updateConstraints()

我希望这能让事情更清楚些。

追加资源:

票数 62
EN

Stack Overflow用户

发布于 2017-12-14 23:45:33

setNeedsUpdateConstraints将根据您所做的更改更新将要更改的约束。例如,如果您的视图有一个相邻的视图,其中有一个水平距离的约束,并且该近邻视图被移除,则该约束现在无效。在这种情况下,您应该删除该约束并调用setNeedsUpdateConstraints。它基本上确保所有约束都是有效的。这不会重新绘制视图。您可以阅读更多关于它的这里

另一方面,setNeedsLayout标记用于重绘的视图,并将其放入动画块中,使绘图成为动画。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47823639

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档