添加、删除和再次添加视图会破坏 AutoLayout 约束

Posted

技术标签:

【中文标题】添加、删除和再次添加视图会破坏 AutoLayout 约束【英文标题】:Adding View, Removing it, and Adding it Again Breaks AutoLayout Constraints 【发布时间】:2015-02-24 02:54:08 【问题描述】:

我有一个非常简单的Custom Container View Controller 可以在两个视图之间切换。添加内容视图(带有 .xib 的 UIViewController 包含一个带有 AutoLayout 约束的按钮)时,它的布局很好,没有冲突的约束。按下按钮将该视图交换为另一个视图(相同类型视图的另一个实例),该视图的布局也很好,没有冲突的约束。

当我再次交换视图以重新插入第一个视图(已存储并且与之前删除的视图相同)时,ios“无法同时满足约束”。每次在第二次交换之后,iOS 都会抛出相同的约束不满足警告。

显示视图控制器的代码:

func displayController(controller:UIViewController) 

    self.addChildViewController(controller)
    controller.view.setTranslatesAutoresizingMaskIntoConstraints(false)
    controller.view.frame = CGRectMake(0.0, 0.0, self.view.bounds.width, self.view.bounds.height)
    self.view.addSubview(controller.view)

    self.view.addConstraint(NSLayoutConstraint(item: controller.view, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: controller.view, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: controller.view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: controller.view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Height, multiplier: 1.0, constant: 0))

    controller.didMoveToParentViewController(self)
    self.currentViewController = controller;

移除视图控制器的代码

func hideController(controller:UIViewController) 

    controller.willMoveToParentViewController(nil)
    controller.view.removeFromSuperview()
    controller.removeFromParentViewController()

    if self.currentViewController == controller 

        self.currentViewController = nil
    

交换视图的代码只调用了这两个方法:

func switchToViewController(controller:UIViewController) 

    if self.currentViewController != nil 

        self.hideController(self.currentViewController!)
    

    self.displayController(controller)

两个子视图控制器使用相同的 .xib 和一个在 InterfaceBuilder 中设置了约束的大按钮。

第一次添加和删除这些子视图时,它们显示正常,没有警告。

再次添加第一个视图后,按钮的高度错误,我收到“无法同时满足约束”警告。

2015-02-23 21:40:17.223 Swift Container View Controller[27976:832141] Unable to simultaneously satisfy constraints.
(
"<NSLayoutConstraint:0x7fa57a41eed0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x7fa57a715b40(667)]>",
"<NSLayoutConstraint:0x7fa57a71d1c0 V:[UIButton:0x7fa57a71bce0'Switch to Yellow View']-(413)-|   (Names: '|':UIView:0x7fa57a71cff0 )>",
"<NSLayoutConstraint:0x7fa57a71d260 V:|-(262)-[UIButton:0x7fa57a71bce0'Switch to Yellow View']   (Names: '|':UIView:0x7fa57a71cff0 )>",
"<NSLayoutConstraint:0x7fa57a71d300 V:[UIButton:0x7fa57a71bce0'Switch to Yellow View'(125)]>",
"<NSLayoutConstraint:0x7fa57a48ff10 UIView:0x7fa57a71cff0.height == UIView:0x7fa57a715b40.height>"
)

我相当肯定按钮上的约束是正确的,因为它们第一次布局正确,但随后会中断。

【问题讨论】:

我打赌你的 xib 是 800 点高? iPhone 6 的高度为 667 点。错误是说它不能使 800 = 667,但为什么它最初没有失败我不能说。 【参考方案1】:

问题在于您以不止一种方式定义了子视图控制器的高度。这三行很重要

"<NSLayoutConstraint:0x7fa57a71d1c0 V:[UIButton:0x7fa57a71bce0'Switch to Yellow View']-(413)-|   (Names: '|':UIView:0x7fa57a71cff0 )>",

这告诉我们按钮的底部被限制在视图的底部,常数(距离)为 413。

"<NSLayoutConstraint:0x7fa57a71d260 V:|-(262)-[UIButton:0x7fa57a71bce0'Switch to Yellow View']   (Names: '|':UIView:0x7fa57a71cff0 )>",

这告诉我们按钮的顶部以 262 的常数(距离)被限制在视图的顶部。

"<NSLayoutConstraint:0x7fa57a71d300 V:[UIButton:0x7fa57a71bce0'Switch to Yellow View'(125)]>",

这告诉我们按钮被限制在一个固定的高度,恒定(距离)为 125。

为了同时满足这三个约束。视图控制器视图的高度必须为 800 (413 + 262 + 125),不多也不少。

当您将视图控制器视图添加到容器时,您正在尝试使用新约束再次定义高度

self.view.addConstraint(NSLayoutConstraint(item: controller.view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Height, multiplier: 1.0, constant: 0))

在日志中显示:

"<NSLayoutConstraint:0x7fa57a41eed0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x7fa57a715b40(667)]>"

由于视图不能同时高度为 667pts 和 800pts,因此必须打破某些约束,并且您的界面显示不正确。

要解决这个问题,我们需要重新考虑按钮周围的约束。答案是不要对按钮使用顶部和底部约束。而是定义按钮的宽度和高度,然后将按钮中心 x 和 y 与视图控制器中心 x 和 y 匹配。

请记住,如果您需要从边到边(即从上到下或导致尾随)链接的(优先级 1000)约束,这将定义超级视图的大小。最好只限制 2 个边和宽度和高度,或者与父级的相对点(例如中心)匹配。

【讨论】:

谢谢@P-double。一旦我修复了约束,就不再需要优先级设置。 没问题。自动布局有时令人头疼:)【参考方案2】:

问题是约束“UIView-Encapsulated-Layout-Height”。这是 SDK 出于未知原因添加的约束。它很少发生,但是当它发生时,我发现的唯一解决方法是给我自己的一个约束优先级 999。在你的情况下:

let heightConstraint = NSLayoutConstraint(item: controller.view, attribute: .Height, relatedBy: .Equal, toItem: self.view, attribute: .Height, multiplier: 1.0, constant: 0)
heightConstraint.priority = 999
self.view.addConstraint( heightConstraint)

SDK 约束只是临时添加的,因此您的布局应该按预期工作。

【讨论】:

谢谢@lassej。做到了。 这可能已经解决了它,但这不是正确的答案 这仅实现丢弃高度约束之一。子视图控制器视图的裁剪帧高度仍为 800

以上是关于添加、删除和再次添加视图会破坏 AutoLayout 约束的主要内容,如果未能解决你的问题,请参考以下文章

删除 viewdisappear 上的子视图出现时再次添加 - 不工作 - ios sdk

在 nativescript-pager 中动态添加项目会破坏视图顺序

UITextField上的限制字符会破坏Emojis,只能添加一个不能删除

在窗口中删除和添加视图时键盘/工具栏中断

VS Code问题:当我保存时,行和空格会自动添加并破坏代码

添加附件会破坏邮件内容轨道