为什么约束更改或动画不需要调用 setNeedsUpdateConstraints? [英] Why calling setNeedsUpdateConstraints isn't needed for constraint changes or animations?

查看:36
本文介绍了为什么约束更改或动画不需要调用 setNeedsUpdateConstraints?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从此答案:

这是已接受的答案建议的动画视图更改:

This is what the accepted answer suggests to animate your view changes:

_addBannerDistanceFromBottomConstraint.constant = 0

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

当我们改变框架时,我们为什么要调用layoutIfNeeded.我们正在更改约束,因此(根据此 other answer) 我们不应该调用 setNeedsUpdateConstraints 吗?

Why do we call layoutIfNeeded when we aren't changing the frames. We are changing the constraints, so (according to this other answer) shouldn't we instead be calling setNeedsUpdateConstraints?

同样,这个备受好评的答案说:

Similarly this highly viewed answer says:

如果后来发生了一些变化,这会使您的其中一个无效约束,您应该立即删除约束并调用setNeedsUpdateConstraints

If something changes later on that invalidates one of your constraints, you should remove the constraint immediately and call 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 不会动画,它只是将视图快速向左移动.

However using setNeedsUpdateConstraints doesn't animate, It just moves the view rapidly to the left.

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 将其向左移动.但是:


If I don't want animation then using either of view.setNeedsLayout or view.setNeedsUpdateConstraints move it to the left. However:

  • 使用view.setNeedsLayout,点击我的按钮后,到达我的viewDidLayoutSubviews 断点.但是永远不会到达 updateViewConstraints 断点.这让我对约束如何更新感到困惑......
  • 使用view.setNeedsUpdateConstraints,点击按钮后,我的updateViewConstraints 断点到达,然后viewDidLayoutSubviews到达断点.这确实有意义,约束被更新,然后 layoutSubviews 被调用.
  • with view.setNeedsLayout, after my button is tapped, my viewDidLayoutSubviews breakpoint is reached. But the updateViewConstraints breakpoint is never reached. This leaves me baffled as to how the constraints are getting updated...
  • with view.setNeedsUpdateConstraints, after the button is tapped my updateViewConstraints breakpoint is reached and then the viewDidLayoutSubviews breakpoint is reached. This does make sense, the constraints are updated, then the layoutSubviews is called.

根据我的阅读:如果您更改约束,那么要使其生效,您必须调用 setNeedsUpdateConstraints,但根据我的观察,这是错误的.拥有以下代码足以制作动画:

Based on my readings: if you change constraints then for it to become effective you MUST call setNeedsUpdateConstraints, but based on my observations that's wrong. Having the following code was enough to animate:

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

为什么?

然后我想也许不知何故它正在通过其他方式更新约束.所以我在 override func updateViewConstraintsoverride func viewDidLayoutSubviews 处放置了一个断点,但只有 viewDidLayoutSubviews 到达了它的断点.

Then I thought maybe somehow under the hoods it's updating the constraints through other means. So I placed a breakpoint at override func updateViewConstraints and override func viewDidLayoutSubviews but only the viewDidLayoutSubviews reached its breakpoint.

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

So how is the Auto Layout engine managing this?

推荐答案

这是 iOS 开发者之间常见的误解.

This is a common misunderstanding among iOS developers.

这是我对自动布局的黄金法则"之一:

Here's one of my "golden rules" for Auto Layout:

永远不需要调用这些方法中的任何一个:

You never need to call any of these methods:

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

除了非常罕见的情况,即您的布局非常复杂,这会降低您的应用速度(或者您故意选择以非典型方式实施布局更改).

except for the very rare case that you have a tremendously complex layout which slows down your app (or you deliberately choose to implement layout changes in an atypical way).

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

Normally, when you want to change your layout, you would activate / deactivate or change layout constraints directly after a button tap or whichever event triggered the change, e.g. in a button's action method:

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

func toggleLayout() {
    isCenteredLayout = !isCenteredLayout

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

正如 Apple 在他们的 自动布局指南:

As Apple puts it in their Auto Layout Guide:

在影响变化发生后立即更新约束几乎总是更清晰、更容易.将这些更改推迟到后面的方法会使代码更加复杂和难以理解.

It is almost always cleaner and easier to update a constraint immediately after the affecting change has occurred. Deferring these changes to a later method makes the code more complex and harder to understand.

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

You can of course also wrap this constraint change in an animation: You first perform the constraint change and then animate the changes by calling layoutIfNeeded() in the animation closure:

@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
    // 1. Perform constraint changes:
    toggleLayout()
    // 2. Animate the changes:
    UIView.animate(withDuration: 1.8, animations: {
        view.layoutIfNeeded()
    }
}

每当您更改约束时,系统都会自动安排延迟布局传递,这意味着系统将在不久的将来重新计算布局.无需调用 setNeedsUpdateConstraints() 因为您只是did 自己更新(更改)约束!需要更新的是布局,即所有视图的框架,不是任何其他约束.

Whenever you change a constraint, the system automatically schedules a deferred layout pass, which means that the system will recompute the layout in the near future. No need to call setNeedsUpdateConstraints() because you just did update (change) the constraint yourself! What needs to be updated is the layout i.e. the frames of all your views, not any other constraint.

如前所述,iOS 布局系统通常不会立即对约束更改做出反应,而只会安排延迟的布局传递.那是出于性能原因.可以这样想:

As previously stated, the iOS layout system usually doesn't react immediately to constraint changes but only schedules a deferred layout pass. That's for performance reasons. Think of it like this:

当您去杂货店购物时,您将一件商品放入购物车,但并未立即付款.相反,您将其他物品放入购物车,直到您觉得自己得到了所需的一切.只有这样,您才能前往收银台并立即支付所有杂货.这样效率更高.

When you go shopping groceries, you put an item in your cart but you don't pay it immediately. Instead, you put other items in your cart until you feel like you got everything you need. Only then you proceed to the cashier and pay all your groceries at once. It's way more efficient.

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

Due to this deferred layout pass there is a special mechanism needed to handle layout changes. I call it The Principle of Invalidation. It's a 2-step mechanism:

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

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

In terms of the layout engine this corresponds to:

  1. setNeedsLayout()
  2. layoutIfNeeded()

  1. setNeedsUpdateConstraints()
  2. updateConstraintsIfNeeded()

第一对方法将导致立即(非延迟)布局传递:首先使布局无效,然后如果布局无效则立即重新计算布局(当然是这样).

The first pair of methods will result in an immediate (not deferred) layout pass: First you invalidate the layout and then you recompute the layout immediately if it's invalid (which it is, of course).

通常你不会担心布局传递是现在发生还是几毫秒后发生,所以你通常只调用 setNeedsLayout() 使布局无效,然后等待延迟的布局传递.这让您有机会对约束执行其他更改,然后稍晚更新布局,但同时更新(→ 购物车).

Usually you don't bother if the layout pass will happen now or a couple of milliseconds later so you normally only call setNeedsLayout() to invalidate the layout and then wait for the deferred layout pass. This gives you the opportunity to perform other changes to your constraints and then update the layout slightly later but all at once (→ shopping cart).

您只需要在需要重新计算布局时立即调用layoutIfNeeded().当您需要根据新布局的结果框架执行一些其他计算时,可能就是这种情况.

You only need to call layoutIfNeeded() when you need the layout to be recomputed right now. That might be the case when you need to perform some other calculations based on the resulting frames of your new layout.

第二对方法将导致立即调用updateConstraints()(在视图上或在视图上updateViewConstraints()控制器).但这是你通常不应该做的事情.

The second pair of methods will result in an immediate call of updateConstraints() (on a view or updateViewConstraints() on a view controller). But that's something you normally shouldn't do.

只有当您的布局真的很慢并且您的 UI 由于您的布局更改而感觉滞后时,您可以选择一种不同于上述方法的方法:而不是直接更新约束以响应按钮点按您只需记下"您要更改的内容,并记下"您的约束需要更新.

Only when your layout is really slow and your UI feels laggy due to your layout changes you can choose a different approach than the one stated above: Rather than updating a constraint directly in response to a button tap you just make a "note" of what you want to change and another "note" that your constraints need to be updated.

@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() 一千次 - 在下一次布局传递期间,您的约束仍然只会更新一次.

This schedules a deferred layout pass and ensures that updateConstraints() / updateViewConstraints() will be called during the layout pass. So you may now even perform other changes and call setNeedsUpdateConstraints() a thousand times – your constraints will still only be updated once during the next layout pass.

现在您覆盖 updateConstraints()/updateViewConstraints() 并根据您当前的布局状态执行必要的约束更改(即您在上面"1."):

Now you override updateConstraints() / updateViewConstraints() and perform the necessary constraint changes based on your current layout state (i.e. what you have "noted" above in "1."):

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

    super.updateConstraints()
}

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

Again, this is only your last resort if the layout is really slow and you're dealing will hundreds or thousands of constraints. I have never needed to use updateConstraints() in any of my projects, yet.

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

I hope this make things a little clearer.

  • 自动布局——从前导到尾随:我在 UIKonf 2017 上的演讲,主题:
    • Auto Layout – From Leading to Trailing: my talk from UIKonf 2017, topics:
      • "The Layout Pass" and
      • "Updating Constraints"
      • 改变约束"
      • 延迟布局通行证"

      这篇关于为什么约束更改或动画不需要调用 setNeedsUpdateConstraints?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆