添加视图、删除视图和再次添加视图会破坏 AutoLayout 约束
Adding View, Removing it, and Adding it Again Breaks AutoLayout Constraints
我有一个非常简单的 Custom Container View Controller 可以在两个视图之间切换。添加内容视图(带有 .xib 的 UIViewController,其中包含一个带有 AutoLayout 约束的按钮)时,它的布局很好,没有约束冲突。按下按钮将该视图交换为另一个视图(同一类型视图的另一个实例),该视图的布局也很好,没有冲突的约束。
当我再次交换视图以重新插入第一个视图(已存储并且与之前删除的视图相同)时 iOS 是 "unable to simultaneously satisfy constraints." 每次在第二次交换后 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 中设置了约束的大按钮。
第一次添加和删除这些子视图时,它们显示正常,没有任何警告。
再次添加第一个视图后,按钮的高度错误,我收到 "unable to simultaneously satisfy constraints" 警告。
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>"
)
我相当确定按钮上的约束是正确的,因为它们在第一次布局时是正确的,但在后续使用时会中断。
问题出在约束 '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 限制只是暂时添加的,因此您的布局应该按预期工作。
问题是您以不止一种方式定义了子视图控制器的高度。这三行很重要
"<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。
为了同时满足这3个约束条件。视图控制器视图的高度必须为 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 边和宽度和高度,或者与父项的相对点(例如中心)匹配。
我有一个非常简单的 Custom Container View Controller 可以在两个视图之间切换。添加内容视图(带有 .xib 的 UIViewController,其中包含一个带有 AutoLayout 约束的按钮)时,它的布局很好,没有约束冲突。按下按钮将该视图交换为另一个视图(同一类型视图的另一个实例),该视图的布局也很好,没有冲突的约束。
当我再次交换视图以重新插入第一个视图(已存储并且与之前删除的视图相同)时 iOS 是 "unable to simultaneously satisfy constraints." 每次在第二次交换后 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 中设置了约束的大按钮。
第一次添加和删除这些子视图时,它们显示正常,没有任何警告。
再次添加第一个视图后,按钮的高度错误,我收到 "unable to simultaneously satisfy constraints" 警告。
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>"
)
我相当确定按钮上的约束是正确的,因为它们在第一次布局时是正确的,但在后续使用时会中断。
问题出在约束 '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 限制只是暂时添加的,因此您的布局应该按预期工作。
问题是您以不止一种方式定义了子视图控制器的高度。这三行很重要
"<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。
为了同时满足这3个约束条件。视图控制器视图的高度必须为 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 边和宽度和高度,或者与父项的相对点(例如中心)匹配。