什么时候可以激活/停用布局约束?


104

我在IB中设置了多组约束,并且我想根据某种状态以编程方式在约束之间进行切换。有一个constraintsA插座集合都标记为已从IB安装,而constraintsB所有插座集合都已从IB 中卸载。

我可以通过编程方式在两组之间切换,如下所示:

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

但是...我不知道何时该做。看来我应该能够一次完成该操作viewDidLoad,但是我无法使其正常工作。我已经打过电话view.updateConstraints(),并view.layoutSubviews()设置约束条件之后,但无济于事。

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

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

2
您是说deactivateConstraints和activateConstraints在viewWillLayoutSubviews中起作用了吗?我试过了,在那儿或在viewDidLoad中都没用。在viewDidAppear中工作过;该视图出现在应放置新约束的位置,但是如果我旋转到横向,该视图将移回到由IB中设置的约束确定的位置(并在旋转回纵向时停留在该位置)。记录约束,显示正确的约束(新激活的约束)。对我来说,这似乎是一个错误。
rdelmar

1
是的,它们是有效的(它们在viewDidAppear中工作),并且不需要调用super,因为没有viewWillLayoutSubviews的默认实现(无论如何我都尝试过调用super,但这没什么区别)。
rdelmar 2014年

1
@rdelmar才有机会进行更多测试...我可以验证我是否确实具有与您描述的相同的行为...首先在viewDidAppear中起作用,但随后在旋转时恢复。
tybro0103

3
显然,您不能为此目的将约束标记为未安装在IB中。在这里找到该信息:stackoverflow.com/questions/27663249/…并且它为我解决了问题。
Stefan 2015年

1
除了在viewDidAppear中激活/停用了其中的一些约束外,我还按照与问题中所述相同的方式实施了约束。这行得通,但是您可以看到元素迅速改变了位置(一个较小但不理想的问题)。在viewWillAppear或viewDidLoad中进行更改无效。但是在阅读了这个问题之后,我尝试在viewDidLayoutSubviews中进行更改。它起作用了,并且位置更改对用户不再可见。(它也适用于viewWillLayoutSubviews)。因此,感谢您的提示!
和平型2015年

Answers:


185

我启用和停用NSLayoutConstraintsviewDidLoad,和我没有任何问题,它。这样就可以了。您的应用程序和我的应用程序之间的设置必须有所不同:-)

我只是描述一下我的设置-也许可以给您带来帮助:

  1. 我成立 @IBOutlets为激活/停用所需的所有约束进行了。
  2. 在里面 ViewController,我将约束保存到不弱的类属性中。这样做的原因是,我发现在取消约束后,我无法重新激活它-它为nil。因此,它似乎在停用时已删除。
  3. NSLayoutConstraint.deactivate/activate不像您一样使用,我使用constraint.active = YES/NO而是。
  4. 设置约束后,我致电view.layoutIfNeeded()

131
“将约束保存到不弱的类属性中”您为我节省了很多时间,谢谢!
OpenUserX03 2015年

10
“我将约束保存到不弱的类属性中”:这节省了我很多的心痛。我不知道我在nil对象上调用选择器。谢谢!!
static0886

4
需要特别注意的是,自动布局不会忽略“无效”约束,它们会被删除。激活/取消激活约束实际上会添加和删除约束。在添加了之前设置的约束后,我.active = false希望花一些时间调试有冲突的自动布局,以期望在将它们设置为活动状态之前将其忽略。
lbarbosa

1
将约束保存到不弱的类属性中,确定,这样可以节省大量时间,而我在没有此条件的情况下得到了一些混合结果。谢啦!
MegaManX

3
苹果医生说:激活或取消激活约束会在视图上调用addConstraint(:)和removeConstraint( :),该视图是此约束所管理项目的最接近的共同祖先。使用此属性,而不是直接调用addConstraint(:)或removeConstraint( :)。因此,似乎取消激活约束后,除非IBOutlet强,否则对约束的剩余引用不强。因此,约束被删除。恕我直言,这几乎是一个错误或至少非常意外的行为。
Olle Raab

52

也许您可以检查一下@properties,替换weakstrong

有时是因为active = NOset self.yourConstraint = nil,所以您不能self.yourConstraint再次使用。


5
如《Swift语言指南》所述,默认情况下,属性是很强的,因此您也可以weak将其删除。
乔纳森·卡布雷拉

30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}

1
因为我的视图控制器是子视图控制器,所以在“ didLayoutSubviews”中执行此操作似乎是唯一可行的方法!仅供参考。
塔尔

这是唯一有效的答案
尤努斯·埃伦·居泽尔

@TalL您是说对子视图控制器本身或其子视图的约束吗?
斯特凡

这是最好的
-ACAkgul

14

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

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

B)如果您希望使用UIView的constraints()功能来访问各种约束,则必须等待viewDidLayoutSubviews()启动并在那里执行,因为这是从笔尖创建视图控制器之后的第一点,即它将具有所有已安装的约束。完成后,别忘了打电话layoutIfNeeded()。这样做的缺点是,如果有任何更改要应用,则布局遍将被执行两次,并且您必须确保不会触发无限循环。

简短的警告: 方法不会返回禁用的约束constraints() !这意味着,如果您确实禁用了某个约束以便稍后再将其重新打开,则需要保留对其的引用。

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


10

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


一个很好的建议。更改约束优先级在viewWillTransition(to:, with:)或中都有效viewWillLayoutSubviews(),您可以将所有其他约束保留为“已安装”在情节提要中。约束优先级可能不会从非必需更改为必需,因此请使用以下值1000。另一方面,激活(添加)和取消激活(删除)约束仅在其中起作用,viewDidLayoutSubviews()并且需要保留strong @IBOutletNSLayoutConstraint-s的引用。
加里

“由于某种原因,更改活动的BOOL不会对我的UI产生任何影响”。基于这里。我认为您不能在运行时更改具有1000优先级的约束。如果要停用它,则应将初始优先级设置为999或更低....
Honey

我不同意此声明,因为它可能导致难以调试问题,并且无法回答问题。将优先级设置为250不会“取消激活”约束,它将仍然有效并影响布局。在大多数情况下,似乎可以“取消激活”约束,但并非在所有情况下都可以。(特别是不是导致我找到该问题答案的情况)
Tumata

这可能会导致崩溃,例如“不支持将优先级从必需更改为未安装约束(反之亦然)。您将优先级设置为250,现有优先级设置为1000。”
卡西克·拉梅什

8

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

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

请记住,它viewWillLayoutSubviews可以多次调用,因此此处无需进行繁琐的计算,好吗?

注意:如果您以后想对某些约束做出反应,请始终存储对其的strong引用。


2
对我而言,唯一可靠的方法是调整中的约束viewDidLayoutSubviews()。调整约束在viewWillLayoutSubviews()我的情况下不起作用。
petrsyn

6

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

  1. loadView
  2. viewDidLoad
  3. viewWillAppear
  4. viewWillLayoutSubviews
  5. viewDidLayoutSubviews
  6. viewDidAppear

现在就您的问题了。

  1. 为什么会出现这种情况?

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

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

答:不可以。原因如上所述。


在对viewController生命周期的描述中,您讨论了如何首先加载视图,然后调用viewDidLoad。但是您还说过,视图不是在调用viewDidLoad时创建的,这显然是矛盾的。另外,您可以自己进行测试,并查看视图是在调用viewDidLoad时创建的,因为您可以向该视图添加子视图。
ABakerSmith 2015年

viewDidLoad应该很好,因为视图是创建和加载的。实际上,激活约束主要取决于性能。我猜测最初的问题与激活约束的位置无关。 stackoverflow.com/questions/19387998/...
加布

@ABakerSmith我已经编辑了答案,以使其更加清晰。
Sumeet

1

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

updateConstraints只要您updateConstraint在第一次初始化和布局后调用s,主要的事情就是不要经常重用重写和分离约束的激活。之后,在视图周期中的哪个位置似乎很重要。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.