当前位置: 首页 > 知识库问答 >
问题:

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

劳麒
2023-03-14

从这个答案中:

以下是公认的答案建议的动画视图更改:

_addBannerDistanceFromBottomConstraint.constant = 0

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

当我们不更改帧时,为什么要调用layoutifneed。我们正在更改约束,所以(根据另一个答案)我们不应该调用setNeedsUpdateConstraints

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

如果以后发生的某些更改使某个约束无效,则应立即删除该约束并调用setNeedsUpdateConstraints

我确实试过两种方法。使用setNeedsLayoutmy view可正确向左设置动画

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!
}

如果我不想要动画,那么使用视图。设置NeedsLayout视图。setNeedsUpdateConstraints将其向左移动。然而:

  • 使用视图。setNeedsLayout,点击我的按钮后,我的viewDidLayoutSubviews断点到达。但是,updateViewConstraints断点永远不会到达。这让我对约束是如何更新的感到困惑
  • 使用视图。setNeedsUpdateConstraints,点击按钮后,到达myupdateViewConstraints断点,然后到达viewDidLayoutSubviews断点。这是有意义的,约束会更新,然后调用LayoutSubview

基于我的阅读:如果你改变了约束,那么为了使其生效,你必须调用setNeedsUpdateConstraints,但基于我的观察,这是错误的。拥有以下代码就足以制作动画:

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

为什么?

然后我想也许在某种程度上,它是在通过其他方式更新约束条件。因此,我在override func updateViewConstraintsoverride func viewDidLayoutSubviews上设置了一个断点,但只有viewDidLayoutSubviews达到了断点。

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

共有3个答案

百里秋月
2023-03-14

我会尽量简单地解释:

首先要记住的是,更新约束不会导致视图布局立即更新。这是出于性能原因,因为布局一切可能需要时间,所以它会“注意”需要发生的变化,然后进行一次布局传递。

更进一步,当影响约束的内容发生变化时,甚至不能更新约束,而只是标记约束需要更新。即使更新约束本身(不布局视图)也可能需要时间,相同的约束可能会改变两种方式(即活动和非活动)。

现在,考虑到setNeedsUpdateConstraints()所做的一切,就是标记视图的约束需要在下一个布局过程之前重新计算,因为它们的某些内容发生了更改,所以它不会使任何约束更改影响当前布局。然后,您应该实现自己版本的updateConstraints()方法,以便根据当前应用程序状态等对约束进行必要的更改。

因此,当系统决定下一次布局传递应该发生时,任何调用了setNeedsUpdateConstraint()的东西(或者系统决定需要更新)都会调用updateConstraint()的实现来进行这些更改。这将在布局完成之前自动发生。

现在,setNeedsLayout()和layoutFneed()类似,只是用于控制实际的布局处理本身。

当影响视图布局的内容发生更改时,可以调用setNeedsLayout(),以便在下一次布局过程中对该视图进行“标记”,重新计算其布局。因此,如果直接更改约束(而不是使用setNeedsUpdateConstraints()和updateConstraints()),则可以调用setNeedsLayout()来指示视图布局已更改,需要在下一次布局过程中重新计算。

layoutIfNeeded()的作用是强制布局过程在此时此地进行,而不是等待系统确定下一次应该进行的时间。这是基于当前所有事物的状态,强制重新计算视图的布局。还要注意,当您执行此操作时,使用setNeedsUpdateConstraints()标记的任何内容都将首先调用其updateConstraints()实现。

因此,在系统决定进行布局传递或您的应用程序调用layoutIfNeed()之前,不会进行布局更改。

在实践中,很少需要使用setNeedsUpdateConstraints()并实现自己版本的updateConstraints(),除非事情非常复杂,您可以直接使用setNeedsLayout()和LayoutFneedd()更新视图约束。

因此,总的来说,不需要调用setNeedsUpdateConstraints来使约束更改生效,事实上,如果您更改约束,当系统决定是时候进行布局传递时,它们将自动生效。

当设置动画时,您希望对正在发生的事情有稍微更多的控制,因为您不希望立即更改布局,而是希望看到它随时间而改变。因此,为了简单起见,假设你有一个动画,需要一秒钟(视图从屏幕的左侧移动到右侧),你更新约束,使视图从左向右移动,但如果这就是你所做的一切,当系统决定是时候进行布局传递时,它只会从一个地方跳到另一个地方。因此,您可以执行以下操作(假设testView是self.view的子视图):

testView.leftPositionConstraint.isActive = false // always de-activate
testView.rightPositionConstraint.isActive = true // before activation
UIView.animate(withDuration: 1) {
    self.view.layoutIfNeeded()
}

让我们把它分解:

首先是testView。leftPositionConstraint。isActive=false关闭将视图保持在左侧位置但尚未调整视图布局的约束。

其次,这是testView。右位置约束。isActive=true打开约束,使视图保持在右侧位置,但视图的布局仍然没有调整。

然后安排动画,并说在动画调用的每个“时间片”期间self.view.layoutIfNeed()。因此,每当动画更新时,它将强制执行self.view的布局传递,导致测试视图布局根据其在动画中的位置重新计算,即在动画的50%之后,布局将介于陈述(当前)布局和所需的新布局之间。

这样,动画就会生效。

所以总的来说:

setNeedsConstraint()-调用以通知系统视图的约束将需要更新,因为影响它们的某些内容已更改。在系统确定需要布局过程或用户强制进行布局过程之前,约束不会实际更新。

updateConstraints()-这应该为视图实现,以根据应用程序状态更新约束。

setNeedsLayout()-这会通知系统影响视图布局(约束可能)的某些内容已更改,需要在下一次布局过程中重新计算布局。当时的布局没有任何变化。

layoutIfNeed()-现在为视图执行布局传递,而不是等待下一个系统计划的布局。此时视图及其子视图的布局实际上将被重新计算。

编辑以更直接地回答以下两个问题:

1)根据我的阅读:如果你改变约束,那么为了使它生效,你必须调用setNeedsUpdateConstraint,但是根据我的观察,这是错误的。有下面的代码就足够动画了:

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

为什么?

首先,你在阅读中误解了你根本不需要使用setNeedsUpdateConstraint。其次,它们已经足够了(假设它们在动画块中),因为setNeedsLayout()标记了self.view需要重新计算它的布局(因此它的子视图布局),而“layoutIfNeed()”强制布局立即发生,因此如果在动画块中,则在动画的每次更新时都要进行布局。

2)然后我想也许在兜帽下,它正在通过其他方式更新约束。所以我在覆盖func updateViewConstraint和覆盖func view的断点,但只有view ddLayoutSubview达到了断点。

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

最好用你最初的例子来说明:

_addBannerDistanceFromBottomConstraint.constant = 0

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

第一行通过更改其常量(无需使用setNeedsUpdateConstraints)更新了约束,但视图的布局(即其实际帧位置和大小)尚未更改。当你调用self时。看法LayoutIfNeed()位于更新self布局的动画块内。根据动画的当前时间帧查看。此时,将计算并调整视图的帧位置/大小。

我希望这能让问题更清楚,但事实上,你的问题在问题的主体部分已经得到了详细的回答,也许解释得太详细了。

现在为了帮助清晰,屏幕上的每个视图都有一个控制其大小和位置的框架。这个框架要么是通过属性手动设置的,要么是使用您设置的约束来计算的。不管是什么方法,都是框架决定视图的位置和大小,而不是约束。约束只是用来计算视图的框架。

为了让它更清楚,我现在将添加两个例子,实现相同的事情,但使用两种不同的方法。对于这两个视图,都有一个testView,它具有将其置于主视图控制器视图中心的约束(这些约束不会改变,在本例中可以有效地忽略)。对于testView,还有一个widthConstraint和一个heightConstraint,用于控制视图的高度和宽度。有一个expandedbool属性,用于确定testView是否展开,还有一个testButton用于在展开和折叠状态之间切换。

第一种方法是:

class ViewController: UIViewController {
    @IBOutlet var testView: UIView!
    @IBOutlet var testButton: UIButton!
    @IBOutlet var widthConstraint: NSLayoutConstraint!
    @IBOutlet var heightConstraint: NSLayoutConstraint!

    var expanded = false

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func testButtonAction(_ sender: Any) {
        self.expanded = !self.expanded

        if self.expanded {
            self.widthConstraint.constant = 200
            self.heightConstraint.constant = 200
        } else {
            self.widthConstraint.constant = 100
            self.heightConstraint.constant = 100
        }
        self.view.layoutIfNeeded() // You only need to do this if you want the layout of the to be updated immediately.  If you leave it out the system will decide the best time to update the layout of the test view.
    }

}

在这里,当点击按钮时,扩展bool属性被切换,然后通过更改它们的常量立即更新约束。然后调用layoutIfNeed来立即重新计算testView的布局(从而更新显示),尽管这可能会被忽略,让系统在需要时根据新的约束值重新计算布局。

下面是做同样事情的另一种方式:

class ViewController: UIViewController {
    @IBOutlet var testView: UIView!
    @IBOutlet var testButton: UIButton!
    @IBOutlet var widthConstraint: NSLayoutConstraint!
    @IBOutlet var heightConstraint: NSLayoutConstraint!

    var expanded = false

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func testButtonAction(_ sender: Any) {
        self.expanded = !self.expanded
        self.view.setNeedsUpdateConstraints()
    }

    override func updateViewConstraints() {
        super.updateViewConstraints()
        if self.expanded {
            self.widthConstraint.constant = 200
            self.heightConstraint.constant = 200
        } else {
            self.widthConstraint.constant = 100
            self.heightConstraint.constant = 100
        }
    }
}

在这里,当点击按钮时,“扩展”bool属性被切换,我们使用updateConstraintsIfNeed向系统标记,在重新计算布局之前,需要更新约束(每当系统确定需要时)。当系统需要知道这些约束来重新计算视图的布局时(由它决定),它会自动调用updateViewConstraint,此时约束将被更改为它们的新值。

因此,如果你尝试一下,这两种方法基本上是一样的,但是它们有不同的用例。

使用方法1允许动画,因为(正如已经指出的)您可以将layoutIfNeed包装在这样的动画块中:

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

这使得系统根据自上次计算布局以来的约束变化在初始布局和新布局之间进行动画处理。

使用方法2可以将更改约束的需要推迟到绝对需要时,如果约束非常复杂(很多),或者可能会发生很多操作,需要在需要下一次布局重新计算之前更改约束,则可以这样做(避免在不需要时不断更改约束)。这样做虽然你没有能力动画的变化,但这可能不是一个问题,因为复杂的约束将使一切缓慢爬行无论如何。

我希望这能有更多帮助。

段干宜
2023-03-14

setNeedsUpdateConstraints将根据您所做的更改更新将要更改的约束。例如,如果视图的相邻视图中存在水平距离约束,并且该相邻视图已被删除,则该约束现在无效。在这种情况下,您应该删除该约束并调用setNeedsUpdateConstraints。它基本上确保所有约束都是有效的。这不会重新绘制视图。你可以在这里了解更多
setNeedsLayout另一方面,标记视图以进行重画,并将其放入动画块中,使图形成为动画。

刘瀚
2023-03-14

这是iOS开发者普遍存在的误解。

以下是我关于自动布局的“黄金法则”:

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

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

除了极少数情况下,你有一个非常复杂的布局,这会减慢你的应用程序(或者你故意选择以非典型的方式实现布局更改)。

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

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

func toggleLayout() {
    isCenteredLayout = !isCenteredLayout

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

正如苹果在《自动布局指南》中所说:

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

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

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

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

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

当你去购物时,你把一件东西放在购物车里,但你没有立即付款。相反,你把其他物品放进购物车,直到你觉得你得到了你需要的一切。然后你去收银台,一次付清所有的杂货。这样效率更高。

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

  1. 你将某物标记为无效。
  2. 如果某物无效,则执行一些操作使其再次有效。

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

  1. setNeedsLayout()
  2. layoutIfNeed()

  1. setNeedsUpdateConstraint()
  2. updateConstraintsIfNeed()

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

通常情况下,您不必担心布局传递是现在发生还是几毫秒后发生,因此通常只需调用setNeedsLayout()使布局无效,然后等待延迟的布局传递。这使您有机会对约束执行其他更改,然后稍微晚一点但同时更新布局(→ 购物车)。

现在需要重新计算布局时,只需调用layoutifneed()。当您需要根据新布局的结果帧执行一些其他计算时,可能会出现这种情况。

第二对方法将导致立即调用updateConstraint()(在视图上或updateViewConstraint()在视图html" target="_blank">控制器上)。但是这是通常不应该做的事情。

只有当你的布局真的很慢,并且你的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()1000次——

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

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

    super.updateConstraints()
}

同样,如果布局非常缓慢,并且您要处理成百上千个约束,那么这只是您的最后手段。我从来没有在我的任何项目中使用过updateConstraints()

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

  • 自动布局-
  • "改变约束"
  • 延迟布局通行证
 类似资料:
  • 问题内容: 从这个答案: 这是被接受的答案建议为您的视图变化设置动画: 不 更改框架时为什么要打电话。我们正在更改约束,所以(根据[另一个答案]我们不应该打电话吗? 如果以后发生的更改使您的约束之一无效,则应立即删除约束并调用setNeedsUpdateConstraints 观察结果: 我实际上确实尝试过同时使用它们。使用我的视图 可以向右正确设置动画 但是,使用 不会 设置动画,它只是将视图

  • 问题内容: 我正在使用来更新旧的应用,并且当没有广告时,它会滑出屏幕。出现广告时,它会在屏幕上滑动。基本的东西。 旧样式,我将帧设置在动画块中。新样式,我对自动布局约束有一个确定Y位置,在这种情况下,它是距父视图底部的距离,并修改常量: 横幅完全按预期移动,但没有动画。 更新:我重新观看了WWDC 12讲的“掌握自动布局的最佳实践” ,其中涵盖了动画。它讨论了如何使用CoreAnimation更新

  • 我正在用更新一个旧的应用程序,当没有广告时,它就会滑出屏幕。当有广告时,它会在屏幕上滑动。基本的东西。 老风格,我设置了一个动画块的框架。新样式中,我有一个到auto-layout约束,它确定位置,在本例中是距离超级视图底部的距离,并修改常数: 和预期的一样,但没有动画。 更新:我重新观看了WWDC12讨论的最佳实践,以掌握自动布局,其中包括动画。它讨论了如何使用CoreAnimation更新约束

  • 我在学习教程中的动画自动布局 http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was 一切都很顺利。 当我尝试在我的应用程序中使用这个概念,尝试从下到上设置设置屏幕(UIView)的动画时,当设置屏幕只是一个空的UIView时,效果非常好, 但如果我将UILabel

  • 所以我正在学习如何使视图从屏幕外变成动画,用作滑块菜单。 我改编了这段代码,从如何在Swift中使用约束对UIView进行动画制作? 什么是自我。看法layoutIfNeeded()部分代码如何? 2.为什么在动画之前和期间编码为2x? 注:如果我注释掉第一个自我。看法layoutIfNeeded()没有任何变化,但如果我注释掉第二个self。看法layoutIfNeeded()移动不再设置动画,

  • 我知道自动布局链基本上由3个不同的过程组成。 更新约束 布局视图(这里是我们计算框架的地方) 显示 我并不完全清楚的是 和 之间的内在区别。来自苹果文档: 设置需要布局 当您想要调整视图子视图的布局时,请在应用程序的主线程上调用此方法。此方法记下请求并立即返回。由于此方法不会强制立即更新,而是等待下一个更新周期,因此您可以使用它来使多个视图的布局失效,然后再更新这些视图中的任何一个。此行为允许您将