AutoLayout:removeFromSuperview / removeConstraints抛出exception,并崩溃困难

我们有select地使用自动布局约束,主要是将标签与可编辑的字段元素(通常是UITextView,UITextField)相关联。 但是,自从实现这些字段的自动布局以来,我们看到了一个令人讨厌的exception,并且在卸载视图,释放等时崩溃。exception正在发生,因为它在卸载之前试图从视图中除去约束。

我们的视图/控制器层次结构是这样的:

UITableViewController (plain style, but with cell appearance to mimic grouped style) --> UITableViewCell ----> UIViewController (container for editable form) ------> UICollectionViewController (editable form) --------> UICollectionViewCell -----------> UIViewController (editable field) --------------> UILabel (field label) **HAS CONSTRAINTS** --------------> UITextView / UITextField (field value) **HAS CONSTRAINTS** 

很多时候,当上层表格单元被释放/replace/重新加载时,我们看到一个巨大的exception,然后崩溃,因为它试图释放/卸载内部的视图层次结构。

我试图通过捕获exception(无帮助)来缓解崩溃,并且在释放/卸载( viewWillDisappear:之前强制删除受影响的视图和所有子视图上的所有约束,帮助。 我甚至试图逐个删除这些约束,看是否有一个特别的问题导致了麻烦,但是当我们调用removeConstraint:或者removeConstraints:在一个容器上准备消失时,所有这些约束都炸毁了。

我很困惑! 这是我们例外的一小部分 – 大约有大约3000条线已经被切掉,所以如果你需要更多的话,请问。

 Exception while deallocating view: { Rows: 0x18911270.posErrorMarker == 4 + 1*0x18911270.negError + 1*0x189112f0.marker + -1*0x189113f0.negError + 1*0x189113f0.posErrorMarker + 1*0x18911a60.marker + -0.5*0x1892dae0.negError + 0.5*0x1892dae0.posErrorMarker + 1*0x18951520.negError + -1*0x18951520.posErrorMarker + -0.5*0x18958090.negError + 0.5*0x18958090.posErrorMarker 0x189112b0.negError == 12 + 1*0x189112b0.posErrorMarker + -1*0x189112f0.marker + 1*0x189113f0.negError + -1*0x189113f0.posErrorMarker + -1*0x18911a60.marker + 1*0x18925530.marker + 0.5*0x1892dae0.negError + -0.5*0x1892dae0.posErrorMarker + 1*0x1893e080.marker + 0.5*0x18958090.negError + -0.5*0x18958090.posErrorMarker + 1*0x18963640.marker 0x18911370.negError == 9 + -1*0x189112f0.marker + 1*0x18911370.posErrorMarker + 1*0x18925530.marker + 1*0x1892dae0.negError + -1*0x1892dae0.posErrorMarker + 1*0x1893e080.marker + 1*0x18963640.marker 0x189113b0.slackMarker == 2 + -1*0x189107d0.marker + 1*0x18910b90.negError + -1*0x18910b90.posErrorMarker + ........ EXPLETIVES DELETED ......... UITableView:0xca2b000.contentHeight == 36 + 1*0xc221c00.marker UITableView:0xca2b000.contentWidth == 704 + 1*0xc239470.marker UITableView:0xca2b000.minX == 0 + 1*0xc2a23f0.marker + -0.5*0xc2a2590.marker UITableView:0xca2b000.minY == 0 + 1*0xc2a25d0.marker + -0.5*0xc2a2630.marker UITableViewCellContentView:0x18ab13d0.Height == 174 + 1*0x18abd4f0.marker UITableViewCellContentView:0x18ab13d0.Width == 704 + 1*0x18abd470.marker ........ EXPLETIVES DELETED ......... <NSAutoresizingMaskLayoutConstraint:0x18988bc0 h=-&- v=-&- UIView:0x18911e50.midY == UIView:0x1892d0c0.midY> Marker:0x18988bc0.marker <NSAutoresizingMaskLayoutConstraint:0x18994b40 h=-&- v=-&- UIView:0xc4a6fb0.midX == UIView:0xc4b4990.midX> Marker:0x18994b40.marker <NSAutoresizingMaskLayoutConstraint:0x18998480 h=-&- v=-&- UIView:0x18915180.width == UIView:0xc4c5970.width> Marker:0x18998480.marker <NSAutoresizingMaskLayoutConstraint:0x18aae320 h=--& v=--& TapSectionalTableViewCell:0x18a3d270.midX == + 352> Marker:0x18aae320.marker <NSAutoresizingMaskLayoutConstraint:0x18aae410 h=--& v=--& H:[TapSectionalTableViewCell:0x18a3d270(704)]> Marker:0x18aae410.marker <NSAutoresizingMaskLayoutConstraint:0x18aae450 h=--& v=--& TapSectionalTableViewCell:0x18a3d270.midY == + 144> Marker:0x18aae450.marker ........ EXPLETIVES DELETED ......... <NSAutoresizingMaskLayoutConstraint:0xc2de2f0 h=--& v=--& TapGenericCollectionCell:0xc2ac500.midX == + 499> Marker:0xc2de2f0.marker <NSAutoresizingMaskLayoutConstraint:0xc2de3b0 h=--& v=--& V:[TapGenericCollectionCell:0xc2ac500(34)]> Marker:0xc2de3b0.marker <NSAutoresizingMaskLayoutConstraint:0xc2de430 h=-&- v=-&- UIView:0x18953f80.height == UIView:0xc2acb20.height> Marker:0xc2de430.marker <NSAutoresizingMaskLayoutConstraint:0xc2de520 h=-&- v=-&- UIView:0x18923af0.height == UIView:0xc2ae570.height> Marker:0xc2de520.marker <NSAutoresizingMaskLayoutConstraint:0xc2de560 h=--& v=--& H:[TapGenericCollectionCell:0xc2ac500(280)]> Marker:0xc2de560.marker ........ EXPLETIVES DELETED ......... <NSContentSizeLayoutConstraint:0xc2f5730 H:[_UIBaselineLayoutStrut:0x18994a30(0)] Hug:250 CompressionResistance:750> Marker:0xc2f5730.posErrorMarker <NSContentSizeLayoutConstraint:0xc2f5730 H:[_UIBaselineLayoutStrut:0x18994a30(0)] Hug:250 CompressionResistance:750> Marker:0xc2f5730.posErrorMarker <NSContentSizeLayoutConstraint:0xc2f5770 V:[_UIBaselineLayoutStrut:0x18994a30(18)] Hug:250 CompressionResistance:750> Marker:0xc2f5770.posErrorMarker internal error. Cannot find an outgoing row head for incoming head UIView:0x189712b0.Width, which should never happen.' 
 /**** BEGIN Individual Field Controller - This code is from the base individual field controller used in our editable form collection *****/ - (void)viewDidLoad { [super viewDidLoad]; self.view.clipsToBounds = YES; self.view.opaque = YES; CGRect viewFrame = self.view.frame; viewFrame.size = [self defaultFieldSize]; self.view.frame = viewFrame; if (self.backgroundColor) { self.view.backgroundColor = self.backgroundColor; } else { self.view.backgroundColor = [UIColor whiteColor]; } [self createLabelAndField]; [self setLabelAndFieldContraints]; [self.view addConstraints:self.labelValueConstraints]; [self.view setNeedsUpdateConstraints]; } - (void)createLabelAndField { [self removeLabelAndField]; UILabel *label = [[UILabel alloc] init]; label.font = self.labelFont; label.textColor = self.labelColor; label.lineBreakMode = NSLineBreakByWordWrapping; label.textAlignment = NSTextAlignmentLeft; label.adjustsFontSizeToFitWidth = NO; label.numberOfLines = 0; if (self.backgroundColor) { label.backgroundColor = self.backgroundColor; } else { label.backgroundColor = [UIColor whiteColor]; } [self.view addSubview:label]; self.label = label; /// EXAMPLE valueView initialization from a subclass that handles long text TapEditableTextView *textView = [[TapEditableTextView alloc] init]; if (self.hasLabelOverValue) { textView.shouldMimicTextField = NO; } else { textView.shouldMimicTextField = YES; } textView.delegate = self; textView.keyboardType = UIKeyboardTypeDefault; textView.font = self.valueFont; textView.textColor = self.valueColor; textView.textAlignment = NSTextAlignmentLeft; textView.normalBackgroundColor = self.backgroundColor; textView.editable = NO; textView.textLines = self.textLines; self.valueTextView = textView; self.valueView = textView; [self.view addSubview:textView]; } - (void)removeLabelAndField { [self clearConstraints]; if (self.label) { [self.label removeFromSuperview]; self.label = nil; } if (self.valueView) { [self.valueView removeFromSuperview]; self.valueView = nil; } } - (void)clearConstraints { if (self.isViewLoaded && self.labelValueConstraints) { [self.view removeConstraints:self.labelValueConstraints]; } self.labelValueConstraints = nil; self.labelToValueHorizConstraint = nil; self.valueWidthConstraint = nil; } // This is called in our field's viewDidLoad, after we've created our label and valueView (UITextField, UITextView, etc) - (void)setLabelAndFieldContraints { [self clearConstraints]; self.labelValueConstraints = [NSMutableArray array]; self.label.translatesAutoresizingMaskIntoConstraints = NO; self.valueView.translatesAutoresizingMaskIntoConstraints = NO; NSLayoutConstraint *constraint = nil; constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:self.labelValueGap]; constraint.priority = UILayoutPriorityRequired; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:0]; constraint.priority = 550; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0]; constraint.priority = 400; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:0]; constraint.priority = UILayoutPriorityRequired; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0]; constraint.priority = 499; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant: -(kDisclosureWidth + self.labelValueGap) ]; constraint.priority = 901; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.label attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:self.labelValueGap]; constraint.priority = UILayoutPriorityDefaultHigh + 1; [self.labelValueConstraints addObject:constraint]; self.labelToValueHorizConstraint = constraint; constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:self.valueView attribute:NSLayoutAttributeBaseline multiplier:1.0f constant:0.f]; constraint.priority = 600; [self.labelValueConstraints addObject:constraint]; constraint = [NSLayoutConstraint constraintWithItem:self.valueView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:(1.f - self.labelWidthPercentage) constant:0]; constraint.priority = 305; [self.labelValueConstraints addObject:constraint]; self.valueWidthConstraint = constraint; [self setCompressionAndHuggingForLabelView:self.label]; [self setCompressionAndHuggingForValueView:self.valueView]; } - (void)setCompressionAndHuggingForLabelView:(UILabel *)labelView { if (!labelView) { return; } [labelView setContentCompressionResistancePriority:510 forAxis:UILayoutConstraintAxisHorizontal]; [labelView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; [labelView setContentHuggingPriority:450 forAxis:UILayoutConstraintAxisHorizontal]; [labelView setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; } - (void)setCompressionAndHuggingForValueView:(UIView *)valueView { if (!valueView) { return; } [valueView setContentCompressionResistancePriority:509 forAxis:UILayoutConstraintAxisHorizontal]; [valueView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; [valueView setContentHuggingPriority:300 forAxis:UILayoutConstraintAxisHorizontal]; [valueView setContentHuggingPriority:650 forAxis:UILayoutConstraintAxisVertical]; } /****** END Individual Field Controller ******/ 

我和一位苹果工程师就此次事故进行了广泛的交谈。

这是两个最可能的原因:

  1. 你有一个无效的约束,比如view1.left = view2.left + 20 ,其中view2意外的是零,或者是乘数为0.确保你的约束条件加倍(和三重),以确保它们是正确的。 这里有两个问题约束的例子:

     // The first constraint would be a problem if view2 were nil [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeBottom multiplier:1 constant:20]; // The second constraint is a problem because the 0 multiplier causes view2 to be "lost" [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeBottom multiplier:0 constant:5]; 
  2. 您在内部基金会自动布局引擎中遇到了与浮点精度累计损失相关的错误。 当你崩溃了,你可以知道这种情况的方式是在控制台的(大)exception日志中search一个非常小(几乎为零)的浮点数,例如:

<505:-7.45058e-08>*PWPlotLegendEntryView:0x600000582be0.Height{id: 34609} +

(在控制台输出中searche-以查找这样的小数字。)这个数字(在这种情况下为-7.45058e-08 )表示在这个特定时间点的系数,而内部引擎正在求解约束。 在这种情况下,数字应该是0,但是由于自动布局引擎使用浮点数进行计算,所以它变成了一个非常微小的负数,而不是把所有东西都吹起来。 如果你能在输出中find这样一个数字,你就知道你已经发现了这个错误。

你怎么能解决这个问题?

更改添加(激活)约束的顺序可能最终会改变内部引擎中的计算顺序,因此可能会导致此问题消失,因为math完成时不会有任何问题的精度损失。

这个问题似乎更频繁地出现在你改变内容压缩阻力或者视图的内容拥抱优先级时,所以试着注释掉任何代码,看看它是否会导致这个bug发生,或者重新命令它早些发生或更高版本的约束设置代码。

有关我的具体情况的更多细节:

我在iOS上遇到了这个崩溃。 重现它的步骤非常有趣:

  1. 包含表格视图的视图控制器被推送到屏幕上(在导航控制器中)。
  2. 表视图必须包含足够的单元格,以便它们不能全部放在可见区域,然后必须滚动到最后一个单元格,然后备份一些(可能这是导致单元格被重用,这是触发这个问题)。
  3. 然后,当包含表视图的视图控制器从导航堆栈中popup时,在popupanimation完成后,应用程序立即在视图控制器视图从视图层次结构中移除的位置崩溃。

经过大量的试验和错误,我能够将问题隔离到一个特定的事情上:在每个表格视图单元格中为UIImageView设置内容压缩阻力和拥抱优先级。 在这种情况下,图像视图使用单元格内的自动布局进行定位,为了实现正确的布局,图像视图需要与其内在的内容大小(图像的大小)完全一致。

这是有问题的代码:

 // Inside of the UITableViewCell's updateConstraints method... [self.imageView setContentCompressionResistancePriority:​UILayoutPriorityRequired forAxis:​UILayoutConstraintAxisHorizontal]; [self.imageView setContentCompressionResistancePriority:​UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; [self.imageView setContentHuggingPriority:​UILayoutPriorityRequired forAxis:​UILayoutConstraintAxisHorizontal]; [self.imageView setContentHuggingPriority:​UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; 

删除上面的代码并将其replace为2个约束(需要优先级),以将图像视图的宽度和高度固定为图像的大小达到相同的结果,但避免了崩溃。 以下是replace代码(使用PureLayout ):

 [self.imageView autoSetDimensionsToSize:self.imageView.image.size]; 

我还发现,只是将有问题的4行移动到我的约束设置代码中的不同位置解决了问题,可能是因为这改变了计算的顺序,以防止有问题的精度损失。

解除分配问题 – 一种可能性

你的代码可以在自动布局上运行,但是可以在主线程上运行,但是在后台运行并且使用你的视图的块(可能是间接的)可能会强有力地引用视图或者它的所有者之一,比如视图控制器Objective-C块的默认行为)。 当这样的块在后台队列上运行并释放时,它所捕获的强引用会在同一个队列上释放,您可能会遇到众所周知的释放问题 。

  1. 在你的视图控制器中,确保你在所有不需要强引用的块中使用弱引用(可能在后台运行)。 你可以这样声明: __weak typeof(self) weakSelf = self; 在块之前 – 在块内使用weakSelf

  2. 同样适用于任何持有对视图的引用的本地variables – 确保将它们的值作为弱引用捕获。

另一种可能性

在我的工作中,当隐藏视图参与布局时,我遇到了类似的问题。 从层次结构中删除视图( -[UIView removeFromSuperview] )而不是将hidden属性设置为YES修复了这个问题。

有同样的问题,通过在IB中一次删除一个约束来解决它,直到崩溃被解决。 这缩小到违规的限制。 然后,我恢复了这个约束,但是颠倒了这些项目:

在这里输入图像描述

你可能是幸运的,能够轻松解决你的AL问题。

对于在任何iOS版本> 8.0中遇到此问题的人,Apple文档声明使用NSLayoutConstraint上的“active”属性,而不是UIView上的removeConstraint / addConstraint函数。 Apple Docs addConstraint参考

让@ smileyborg真棒的答案更具有可操作性:

这可能会发生,如果你有任何乘法器的限制,可能会遇到浮点精度问题。

解决:

  1. 检查所有有乘数的约束(在布局代码中,或手动编辑故事板/笔尖并searchmultiplier= )。
  2. 如果乘数不是两个浮点数的“漂亮”次数,则将其转到最近的一个(可以使用浮点计算器 )

要简单的做2,就是input你想要的数值和计算器,然后closures尾数中较低的精度位,直到该值与计算器底部的四舍五入的值相匹配。

在我的情况下,它是一个8:9乘法器的比例宽度约束。 我把它改为7:9,它工作。

顺便说一句,find一个约束最简单的方法是开始从视图控制器中删除视图。 使用二进制algorithm:)删除一半的意见,然后一半,使应用程序崩溃等

对我来说,问题是我在适合我的时候删除了一个约束,在调用dequeueReusableCellWithReuseIdentifier来设置我的UICollectionViewCell的属性之后。 解决方法是改为调用:

  [_myUICollectionViewCell setNeedsUpdateConstraints]; 

并覆盖:

  -(void)updateConstraints 

并在那里做我的杂事。 似乎你不能随心所欲地移除约束条件。

我只是偶然发现了OSX Mavericks下的一个OSX应用程序的错误,但与其他的答案不同,我绝对没有任何其他的线程与UI对象交互,而且问题中的视图层次结构是可见的太。 我也不使用块。 奇怪的是,当我删除NSTextField的垂直约束时,问题就消失了。

FWIW从超视图中删除的有问题的视图导致“内部错误,无法find即将到来的头部的行头”错误是许多侧面控件之一,这些侧面控件共同呈现主视图中可被剪切的对象的属性,复制,创build等。这意味着用户可以快速地将新的对象粘贴到主视图中,这意味着侧面板的控件正在被破坏,并且新的对象也被快速创build。 当然,在主线中的一切,这应该不会有所作为,但似乎。

造成问题的确切约束是

[self addConstraint:[NSLayoutConstraint constraintWithItem:control attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:other attribute:NSLayoutAttributeHeight multiplier:1.4 constant:0.0]];

其中控制是(可编辑的)NSTextField导致问题,“其他”是另一个(不可编辑的)NSTextField标签。

我得到这个问题与MZFormSheetController : https : //github.com/m1entus/MZFormSheetController/issues/78

此代码崩溃:

 [formSheetController.view addSubview:self.sharePanel]; // ... [self.sharePanel removeFromSuperview]; // <-- CRASHES HERE 

我的解决scheme很奇怪,但它的工作原理:

 [self.sharePanel removeFromSuperview]; // <-- This line helps to avoid crash [formSheetController.view addSubview:self.sharePanel]; // ... [self.sharePanel removeFromSuperview]; 

这里是sharePanel属性声明:

 @property (weak, nonatomic) IBOutlet UIView *sharePanel; 

当我调用removeConstraints时,我得到这个崩溃:与零参数。

当我在wAnyhAny模式下仍然有一个缺失的约束时,我得到了这个崩溃,修复这个删除了错误。

正如在这个线程中的其他答案表明这是一个无效的自动布局/限制问题,虽然它似乎是非常挑剔的“无效”的资格。

幸运的是,自从我上次提交以来,我并没有做出太多改变,并能够追查这些违规的更改。 对于我来说,有10个水平的UIImageView宽度相同和固定的2:3纵横比的观点是问题。

崩溃只是在离开包含这个图像行的UIViewController后才发生。 每个UIImageView被设置为UIViewContentModeScaleAspectFill 。 删除这个内容模式的改变(这是在UIImage设置之前完成的)似乎解决了我的问题,但不是一个可接受的解决scheme。 我最终删除了宽高比限制,并为每个图像使用固定的宽度和高度。

为什么这是崩溃我的应用程序,我不知道…崩溃也可能只能在运行iOS 7.1.2的iPhone 4s上重现。 我试图在运行iOS 9.1的iPhone 4s模拟器上重现相同的崩溃而没有成功。 在运行iOS 9.1的phsyical iPhone 5上运行时,它也不会崩溃。

希望能帮助那里的人

根据苹果文档:

在为iOS 8.0或更高版本开发时,将约束的活动属性设置为YES,而不是直接调用addConstraint:方法。 活动属性自动添加并从正确的视图中删除约束。

在我的情况下,我不得不修改宽度约束

 for var constraint in self.navigationBar.constraints { if constraint.identifier == "theProgressWidth" { let sizeWidth = self.navigationBar.frame.size.width constraint = NSLayoutConstraint(item: progress!, attribute: .Width, relatedBy: .Equal, toItem: self.navigationBar, attribute: .Width, multiplier: ((sizeWidth * (level / 100)) / sizeWidth), constant: 0) constraint.active = true } }