【问题标题】:When can I activate/deactivate layout constraints?我什么时候可以激活/停用布局约束?
【发布时间】:2015-02-14 04:02:52
【问题描述】:

我在 IB 中设置了多组约束,我想根据某些状态以编程方式在它们之间切换。有一个constraintsA outlet 集合,所有这些集合都标记为从 IB 安装,还有一个 constraintsB outlet 集合,所有这些集合都在 IB 中被卸载。

我可以像这样以编程方式在两组之间切换:

NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)

但是...我不知道什么时候可以这样做。似乎我应该能够在viewDidLoad 中做到这一点,但我无法让它发挥作用。设置约束后,我尝试调用view.updateConstraints()view.layoutSubviews(),但无济于事。

我确实发现,如果我在 viewDidLayoutSubviews 中设置约束,一切都会按预期工作。我想我想知道两件事......

  1. 为什么会出现这种行为?
  2. 是否可以从 viewDidLoad 激活/停用约束?

【问题讨论】:

  • 你的意思是deactivateConstraints和activateConstraints在viewWillLayoutSubviews中工作吗?我试过了,但它在那里或 viewDidLoad 中都不起作用。它在 viewDidAppear 中起作用;视图出现在新约束应该放置的位置,但是如果我旋转到横向,视图会移回由 IB 中设置的约束确定的位置(并在我旋转回纵向时停留在那里)。记录约束,显示正确的约束(新激活的约束)。这对我来说似乎是一个错误。
  • 是的,它们是有效的(它们在 viewDidAppear 中工作),并且没有必要调用 super,因为没有 viewWillLayoutSubviews 的默认实现(无论如何我尝试调用 super,但这没有区别) .
  • @rdelmar 刚刚有机会进行更多测试...我可以验证我实际上得到了您描述的相同行为...最初在 viewDidAppear 中有效,但随后在轮换时恢复。
  • 显然您不能为此目的将约束标记为未在 IB 中安装。在此处找到该信息:stackoverflow.com/questions/27663249/…,它为我解决了问题。
  • 我的约束实现方式与问题中描述的相同,但我在 viewDidAppear 中激活/停用了其中的一些。这行得通,但是您可以看到元素迅速改变位置(一个小但不受欢迎的问题)。在 viewWillAppear 或 viewDidLoad 中进行更改不起作用。但是在阅读了这个问题之后,我尝试在 viewDidLayoutSubviews 中进行更改。它起作用了,用户不再看到位置变化。 (它也适用于 viewWillLayoutSubviews)。所以谢谢你的提示!

标签: ios swift uiview autolayout nslayoutconstraint


【解决方案1】:
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}

【讨论】:

  • 因为我的视图控制器是一个子视图控制器——在“didLayoutSubviews”中这样做似乎是唯一可行的方法!仅供参考。
  • 这是唯一有效的答案
  • @TalL 你的意思是对子视图控制器本身的约束,还是它的子视图?
  • 这是最好的一个
【解决方案2】:

我在viewDidLoad 中激活和停用NSLayoutConstraints,我没有任何问题。所以它确实有效。您的应用程序和我的应用程序之间的设置必须有所不同:-)

我将仅描述我的设置 - 也许它可以为您提供指导:

  1. 我为我需要激活/停用的所有约束设置了@IBOutlets
  2. ViewController 中,我将约束保存到不弱的类属性中。这样做的原因是我发现在停用约束后,我无法重新激活它 - 它是 nil。所以,它似乎在停用时被删除了。
  3. 我不像你那样使用NSLayoutConstraint.deactivate/activate,而是使用constraint.active = YES/NO
  4. 设置约束后,我调用view.layoutIfNeeded()

【讨论】:

  • "将约束保存到不弱的类属性中" 你为我节省了很多时间,谢谢!
  • “我将约束保存到不弱的类属性中”:这让我省了很多心痛。我不知道我在 nil 对象上调用选择器。谢谢!!
  • 重要的是要注意自动布局不会忽略“非活动”约束,它们会被删除。激活/停用约束实际上是添加和删除它们。在我添加了我之前设置的约束 .active = false 之后,我花了一些时间调试一个冲突的自动布局,期望它们会被忽略,直到我将它们设置为活动状态。
  • 将约束保存到不弱的类属性中,好的,这节省了很多时间,没有这个我得到了一些混合的结果。谢谢大佬!
  • Apples 文档说:在视图上激活或停用约束调用 addConstraint(:) 和 removeConstraint(:) 是受此约束管理的项目的最近共同祖先.使用此属性而不是直接调用 addConstraint(:) 或 removeConstraint(:)。因此,似乎当一个约束被停用时,它被删除,然后没有对约束的强引用,除非 IBOutlet 是强的。因此,约束被删除。恕我直言,这几乎是一个错误或至少是非常意外的行为。
【解决方案3】:

我相信您遇到的问题是由于在调用 viewDidLoad() 之后才将约束添加到他们的视图中。您有多种选择:

A) 您可以将布局约束连接到 IBOutlet 并通过这些引用在代码中访问它们。由于插座在viewDidLoad() 启动之前已连接,因此约束应该是可访问的,您可以继续在那里激活和停用它们。

B) 如果您希望使用 UIView 的 constraints() 函数来访问各种约束,您必须等待 viewDidLayoutSubviews() 启动并在那里执行,因为这是创建后的第一点来自 nib 的视图控制器,它将具有任何已安装的约束。完成后不要忘记致电layoutIfNeeded()。这样做的缺点是,如果要应用任何更改,布局传递将执行两次,并且您必须确保不会触发无限循环。

简短的警告: constraints() 方法不会返回禁用的约束!这意味着,如果您确实禁用了约束并打算稍后再次打开它,您将需要保留对它的引用。

C) 您可以忘记情节提要方法,而是手动添加约束。由于您在viewDidLoad() 中执行此操作,因此我假设目的是在对象的整个生命周期内只执行一次,而不是动态更改布局,因此这应该是一种可接受的方法。

【讨论】:

    【解决方案4】:

    创建视图时,会依次调用以下生命周期方法:

    1. 加载视图
    2. viewDidLoad
    3. viewWillAppear
    4. viewWillLayoutSubviews
    5. viewDidLayoutSubviews
    6. viewDidAppear

    现在回答你的问题。

    1. 为什么会出现这种行为?

    Answer:因为当您尝试在viewDidLoad 中设置视图的约束时,视图没有边界,因此无法设置约束。只有在viewDidLayoutSubviews 之后,视图的边界才最终确定。

    1. 是否可以从 viewDidLoad 激活/停用约束?

    答案:没有。原因如上。

    【讨论】:

    • 在您对 viewController 生命周期的描述中,您谈到了如何首先加载视图,然后调用 viewDidLoad。但是您还说视图不是在调用 viewDidLoad 时创建的,这显然是矛盾的。此外,您可以自己测试并查看视图是否已在调用 viewDidLoad 时创建,因为您可以将子视图添加到视图。
    • viewDidLoad 应该没问题,因为视图是创建和加载的......实际上,您激活约束主要归结为性能。我猜最初的问题与激活约束的位置无关。 stackoverflow.com/questions/19387998/…
    • @ABakerSmith 我已经编辑了我的答案以更清楚。
    【解决方案5】:

    我发现只要您在- (void)updateConstraints(目标c)的覆盖中设置每个法线的约束,并使用strong 引用初始使用活动和非活动约束。在视图周期的其他地方停用和/或激活您需要的内容,然后调用layoutIfNeeded,您应该没有问题。

    主要的是不要不断地重用updateConstraints的覆盖和分离约束的激活,只要你在第一次初始化和布局之后调用updateConstraints。之后在视图周期中的哪个位置似乎很重要。

    【讨论】:

      【解决方案6】:

      停用未使用的约束的适当时间:

      -(void)viewWillLayoutSubviews{
          [super viewWillLayoutSubviews];
      
          self.myLittleConstraint.active = NO;
      }
      

      请记住,viewWillLayoutSubviews 可以被多次调用,所以这里没有繁重的计算,好吗?

      注意:如果您想稍后响应某些约束,请始终存储 strong 对它们的引用。

      【讨论】:

      • 对我来说唯一可靠的方法是调整viewDidLayoutSubviews() 中的约束。在我的情况下,调整 viewWillLayoutSubviews() 中的约束不起作用。
      【解决方案7】:

      您还可以将priority 属性调整为“启用”和“禁用”它们(例如,启用 750 值和禁用 250 值)。出于某种原因,更改 active BOOL 对我的 UI 没有任何影响。不需要layoutIfNeeded,可以在 viewDidLoad 或之后的任何时间设置和更改。

      【讨论】:

      • 一个非常好的建议。更改约束优先级在viewWillTransition(to:, with:)viewWillLayoutSubviews() 中有效,您可以将所有替代约束保持为“已安装”在情节提要中。约束优先级可能不会从非必需变为必需,因此请使用低于1000 的值。另一方面,激活(添加)和停用(删除)约束仅在 viewDidLayoutSubviews() 中有效,并且需要保持 strong @IBOutletNSLayoutConstraint-s 的引用。
      • “由于某种原因,更改活动 BOOL 对我的 UI 没有任何影响”。基于here。我认为您不能在运行时更改优先级为 1000 的约束。如果要停用它,则应将初始优先级设置为 999 或更低....
      • 我不同意这种说法,因为它可能导致难以调试的问题并且无法回答问题。将优先级设置为 250 不会“停用”约束,它仍然会生效并影响布局。在大多数情况下,它似乎“停用”了约束,但绝对不是在所有情况下。 (特别是,不是让我找到这个问题的答案的情况)
      • 它可能会导致崩溃以及“不支持将优先级从必需更改为不安装约束(反之亦然)。您通过了优先级 250,现有优先级为 1000。”
      【解决方案8】:

      也许您可以检查您的@properties,将weak 替换为strong

      有时是因为active = NO 设置了self.yourConstraint = nil,所以你不能再次使用self.yourConstraint

      【讨论】:

      • Swift Language Guide 中所述,默认情况下属性很强大,因此您也可以删除weak 即可。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-03
      • 1970-01-01
      • 2018-08-07
      • 1970-01-01
      相关资源
      最近更新 更多