AutoLayout:removeFromSuperview/removeConstraints 抛出异常并严重崩溃 [英] AutoLayout: removeFromSuperview / removeConstraints throws exception and crashes hard

查看:27
本文介绍了AutoLayout:removeFromSuperview/removeConstraints 抛出异常并严重崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有选择地使用自动布局约束,主要是根据可编辑字段元素(通常为 UITextView、UITextField)定位标签.然而,由于为这些字段实现了自动布局,我们会在卸载视图、解除分配等时看到令人讨厌的异常和崩溃.异常发生是因为它试图在卸载视图之前从视图中删除约束.

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

UITableViewController(简单的样式,但单元格外观模仿分组样式)-->UITableViewCell---->UIViewController(可编辑表单的容器)------>UICollectionViewController(可编辑表单)-------->UICollectionViewCell----------->UIViewController(可编辑字段)-------------->UILabel(字段标签)**有约束**-------------->UITextView/UITextField(字段值)**有约束**

很多时候,当上层表格单元格被释放/替换/重新加载时,我们会看到一个巨大的异常,然后当它试图释放/卸载其中的视图层次结构时崩溃.

我试图通过捕获异常(没有帮助)以及在解除分配/卸载之前强行删除受影响视图和所有子视图上的所有约束(在 viewWillDisappear 中:) 并且它似乎没有帮助.我什至试图一一删除这些约束,看看是否有特定的约束会导致问题,但当我们调用 removeConstraint:removeConstraints:在容器上准备消失.

我很困惑!这是我们的例外情况的一个片段——大约有 3000 行被删掉了,所以如果你需要更多,尽管问.

解除分配视图时出现异常:{ 行:0x18911270.posErrorMarker == 4 + 1*0x18911270.negError + 1*0x189112f0.marker + -1*0x189113f0.negError + 1*0x189113f0.posErrorMarker + 1*0x189113f0.posErrorMarker + 1*1*20x0x0x0.marker+89.50x0x0x0x0.marker+89.50x0x0x0x0x80x0x0x0x80+ 1*0x18951520.negError + -1*0x18951520.posErrorMarker + -0.5*0x18958090.negError + 0.5*0x18958090.posErrorMarker0x189112b0.negError == 12 + 1*0x189112b0.posErrorMarker + -1*0x189112f0.marker + 1*0x189113f0.negError + -1*0x189113f0.posErrorMarker + -1*0x189113f0.posErrorMarker + -1*50x9.5ex9.18.50x96e.posErrorMarker + -1*0x189112f0.markernegError + -0.5*0x1892dae0.posErrorMarker + 1*0x1893e080.marker + 0.5*0x18958090.negError + -0.5*0x18958090.posErrorMarker + 1*0x18963640.0x18911370.negError == 9 + -1*0x189112f0.marker + 1*0x18911370.posErrorMarker + 1*0x18925530.marker + 1*0x1892dae0.negError + -1*0x0x0.820.marker*0x0x18030200x1370marker+-1*0x0x1370.posErrorMarker0x189113b0.slackMarker == 2 + -1*0x189107d0.marker + 1*0x18910b90.negError + -1*0x18910b90.posErrorMarker +...... 删除了脏话......UITableView:0xca2b000.contentHeight == 36 + 1*0xc221c00.markerUITableView:0xca2b000.contentWidth == 704 + 1*0xc239470.markerUITableView:0xca2b000.minX == 0 + 1*0xc2a23f0.marker + -0.5*0xc2a2590.markerUITableView:0xca2b000.minY == 0 + 1*0xc2a25d0.marker + -0.5*0xc2a2630.markerUITableViewCellContentView:0x18ab13d0.Height == 174 + 1*0x18abd4f0.markerUITableViewCellContentView:0x18ab13d0.Width == 704 + 1*0x18abd470.marker...... 删除了脏话......<NSAutoresizingMaskLayoutConstraint:0x18988bc0 h=-&- v=-&- UIView:0x18911e50.midY == UIView:0x1892d0c0.midY>标记:0x18988bc0.marker<NSAutoresizingMaskLayoutConstraint:0x18994b40 h=-&- v=-&- UIView:0xc4a6fb0.midX == UIView:0xc4b4990.midX>标记:0x18994b40.marker<NSAutoresizingMaskLayoutConstraint:0x18998480 h=-&- v=-&- UIView:0x18915180.width == UIView:0xc4c5970.width>标记:0x18998480.marker<NSAutoresizingMaskLayoutConstraint:0x18aae320 h=--&v=--&TapSectionalTableViewCell:0x18a3d270.midX == + 352>标记:0x18aae320.marker<NSAutoresizingMaskLayoutConstraint:0x18aae410 h=--&v=--&H:[TapSectionalTableViewCell:0x18a3d270(704)]>标记:0x18aae410.marker<NSAutoresizingMaskLayoutConstraint:0x18aae450 h=--&v=--&TapSectionalTableViewCell:0x18a3d270.midY == + 144>标记:0x18aae450.marker...... 删除了脏话......<NSAutoresizingMaskLayoutConstraint:0xc2de2f0 h=--&v=--&TapGenericCollectionCell:0xc2ac500.midX == + 499>标记:0xc2de2f0.marker<NSAutoresizingMaskLayoutConstraint:0xc2de3b0 h=--&v=--&V:[TapGenericCollectionCell:0xc2ac500(34)]>标记:0xc2de3b0.marker<NSAutoresizingMaskLayoutConstraint:0xc2de430 h=-&- v=-&- UIView:0x18953f80.height == UIView:0xc2acb20.height>标记:0xc2de430.marker<NSAutoresizingMaskLayoutConstraint:0xc2de520 h=-&- v=-&- UIView:0x18923af0.height == UIView:0xc2ae570.height>标记:0xc2de520.marker<NSAutoresizingMaskLayoutConstraint:0xc2de560 h=--&v=--&H:[TapGenericCollectionCell:0xc2ac500(280)]>标记:0xc2de560.marker...... 删除了脏话......<NSContentSizeLayoutConstraint:0xc2f5730 H:[_UIBaselineLayoutStrut:0x18994a30(0)] Hug:250 CompressionResistance:750>标记:0xc2f5730.posErrorMarker<NSContentSizeLayoutConstraint:0xc2f5730 H:[_UIBaselineLayoutStrut:0x18994a30(0)] Hug:250 CompressionResistance:750>标记:0xc2f5730.posErrorMarker<NSContentSizeLayoutConstraint:0xc2f5770 V:[_UIBaselineLayoutStrut:0x18994a30(18)] Hug:250 CompressionResistance:750>标记:0xc2f5770.posErrorMarker内部错误.找不到用于传入头 UIView:0x189712b0.Width 的传出行头,这永远不会发生.

/**** BEGIN Individual Field Controller - 此代码来自我们可编辑表单集合中使用的基本单个字段控制器 *****/- (void)viewDidLoad {[超级viewDidLoad];self.view.clipsToBounds = YES;self.view.opaque = 是;CGRect viewFrame = self.view.frame;viewFrame.size = [self defaultFieldSize];self.view.frame = viewFrame;如果(self.backgroundColor){self.view.backgroundColor = self.backgroundColor;}别的 {self.view.backgroundColor = [UIColor whiteColor];}[自己创建标签和字段];[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;如果(self.backgroundColor){label.backgroundColor = self.backgroundColor;}别的 {label.backgroundColor = [UIColor whiteColor];}[self.view addSubview:label];self.label = 标签;///示例 valueView 从处理长文本的子类初始化TapEditableTextView *textView = [[TapEditableTextView alloc] init];如果(self.hasLabelOverValue){textView.shouldMimicTextField = NO;}别的 {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];如果(自我标签){[self.label removeFromSuperview];self.label = nil;}如果(self.valueView){[self.valueView removeFromSuperview];self.valueView = nil;}}- (void)clearConstraints {如果(self.isViewLoaded && self.labelValueConstraints){[self.view removeConstraints:self.labelValueConstraints];}self.labelValueConstraints = nil;self.labelToValueHorizConstraint = nil;self.valueWidthConstraint = nil;}//在我们创建标签和值视图(UITextField、UITextView 等)之后,在我们字段的 viewDidLoad 中调用它- (void)setLabelAndFieldContraints {[self clearConstraints];self.labelValueConstraints = [NSMutableArray 数组];self.label.translatesAutoresizingMaskIntoConstraints = NO;self.valueView.translatesAutoresizingMaskIntoConstraints = NO;NSLayoutConstraint *constraint = nil;约束 = [NSLayoutConstraintconstraintWithItem:self.label 属性:NSLayoutAttributeLeft相关者:NSLayoutRelationEqualtoItem:self.view 属性:NSLayoutAttributeLeft乘数:1.0f 常数:self.labelValueGap];约束.priority = UILayoutPriorityRequired;[self.labelValueConstraints addObject:constraint];约束 = [NSLayoutConstraintconstraintWithItem:self.label 属性:NSLayoutAttributeTop相关者:NSLayoutRelationEqualtoItem:self.view 属性:NSLayoutAttributeTop乘数:1.0f 常数:0];约束.优先级= 550;[self.labelValueConstraints addObject:constraint];约束 = [NSLayoutConstraintconstraintWithItem:self.label 属性:NSLayoutAttributeBottom相关者:NSLayoutRelationEqualtoItem:self.view 属性:NSLayoutAttributeBottom乘数:1.0f 常数:0];约束.优先级= 400;[self.labelValueConstraints addObject:constraint];约束 = [NSLayoutConstraintconstraintWithItem:self.valueView 属性:NSLayoutAttributeTop相关者:NSLayoutRelationEqualtoItem:self.view 属性:NSLayoutAttributeTop乘数:1.0f 常数:0];约束.priority = UILayoutPriorityRequired;[self.labelValueConstraints addObject:constraint];约束 = [NSLayoutConstraintconstraintWithItem:self.valueView 属性:NSLayoutAttributeBottom相关者:NSLayoutRelationEqualtoItem:self.view 属性:NSLayoutAttributeBottom乘数:1.0f 常数:0];约束.优先级= 499;[self.labelValueConstraints addObject:constraint];约束 = [NSLayoutConstraintconstraintWithItem:self.valueView 属性:NSLayoutAttributeRight相关者:NSLayoutRelationEqualtoItem:self.view 属性:NSLayoutAttributeRight乘数:1.0f 常数:-(kDisclosureWidth + self.labelValueGap) ];约束.优先级= 901;[self.labelValueConstraints addObject:constraint];约束 = [NSLayoutConstraintconstraintWithItem:self.valueView 属性:NSLayoutAttributeLeading相关者:NSLayoutRelationGreaterThanOrEqualtoItem:self.label 属性:NSLayoutAttributeTrailing乘数:1.0f 常数:self.labelValueGap];约束.priority = UILayoutPriorityDefaultHigh + 1;[self.labelValueConstraints addObject:constraint];self.labelToValueHorizConstraint = 约束;约束 = [NSLayoutConstraintconstraintWithItem:self.label 属性:NSLayoutAttributeBaseline相关者:NSLayoutRelationEqualtoItem:self.valueView 属性:NSLayoutAttributeBaseline乘数:1.0f 常数:0.f];约束.优先级= 600;[self.labelValueConstraints addObject:constraint];约束 = [NSLayoutConstraintconstraintWithItem:self.valueView 属性:NSLayoutAttributeWidth相关者:NSLayoutRelationEqualtoItem:self.view 属性:NSLayoutAttributeWidth乘数:(1.f - self.labelWidthPercentage)常数:0];约束.优先级= 305;[self.labelValueConstraints addObject:constraint];self.valueWidthConstraint = 约束;[self setCompressionAndHuggingForLabelView:self.label];[self setCompressionAndHuggingForValueView:self.valueView];}- (void)setCompressionAndHuggingForLabelView:(UILabel *)labelView {如果(!labelView){返回;}[labelView setContentCompressionResistancePriority:510 forAxis:UILayoutConstraintAxisHorizo​​ntal];[labelView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];[labelView setContentHuggingPriority:450 forAxis:UILayoutConstraintAxisHorizo​​ntal];[labelView setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];}- (void)setCompressionAndHuggingForValueView:(UIView *)valueView {如果(!valueView){返回;}[valueView setContentCompressionResistancePriority:509 forAxis:UILayoutConstraintAxisHorizo​​ntal];[valueView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];[valueView setContentHuggingPriority:300 forAxis:UILayoutConstraintAxisHorizo​​ntal];[valueView setContentHuggingPriority:650 forAxis:UILayoutConstraintAxisVertical];}/****** 结束个人现场控制器 ******/

解决方案

我与 Apple 工程师就这次崩溃进行了(广泛的)对话.

以下是两个最可能的原因:

  1. 您有一个无效的约束,例如 view1.left = view2.left + 20 其中 view2 意外为零,或者乘数为 0.一定要双重(和三次)检查你的约束以确保它们是正确的.以下是有问题的约束的 2 个示例:

    //如果 view2 为 nil,第一个约束就会有问题[NSLayoutConstraint constraintWithItem:view1 属性:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 属性:NSLayoutAttributeBottom 乘数:1 常量:20];//第二个约束有问题,因为0乘数导致view2丢失"[NSLayoutConstraint constraintWithItem:view1 属性:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 属性:NSLayoutAttributeBottom multiplier:0 constant:5];

  2. 您在内部 Foundation 自动布局引擎中遇到了与浮点精度累积损失相关的错误.当您崩溃时,您可以知道这种情况的方法是在控制台中的(大)异常日志中搜索一个非常小的(接近零)浮点数,例如:

    莉>

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

(在控制台输出中搜索 e- 以找到像这样的小数字.)这个数字(在本例中为 -7.45058e-08)表示在在内部引擎解决约束时的这个特定时间点.在这种情况下,数字应该正好是 0,但是由于自动布局引擎使用浮点数进行计算的方式,它变成了一个非常小的负数,而不是把所有东西都炸掉.如果你能在输出中找到这样的数字,你就知道你遇到了这个错误.

您如何解决此问题?

更改您添加(激活)约束的顺序可能最终会更改内部引擎中的计算顺序,结果可能会导致此问题消失,因为在完成数学运算时不会出现任何精度损失问题.

>

当您更改视图的内容压缩阻力或内容拥抱优先级时,此问题似乎会更频繁地出现,因此请尝试注释掉任何执行此操作的代码以查看它是否导致此错误发生,或对其重新排序在您的约束设置代码中更早或更晚发生.

有关我的具体案例的更多详细信息:

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

  1. 一个包含表格视图的视图控制器被推送到屏幕上(在导航控制器中).
  2. 表格视图必须包含足够的单元格,因此它们无法全部放入可见区域,然后必须将其滚动到最后一个单元格,然后再向后退一点(大概,这会导致单元格被重用,这引发了这个问题).
  3. 然后,当包含表格视图的视图控制器从导航堆栈中弹出时,在弹出动画完成后,应用程序会立即在视图控制器的视图从视图层次结构中移除时崩溃.

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

这是有问题的代码:

//UITableViewCell 的 updateConstraints 方法内部...[self.imageView setContentCompressionResistancePriority: UILayoutPriorityRequired forAxis: UILayoutConstraintAxisHorizo​​ntal];[self.imageView setContentCompressionResistancePriority: UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];[self.imageView setContentHuggingPriority: UILayoutPriorityRequired forAxis: UILayoutConstraintAxisHorizo​​ntal];[self.imageView setContentHuggingPriority: UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

删除上面的代码并用 2 个约束替换它(在必需的优先级)以固定宽度 &图像视图的高度与图像的大小达到相同的结果,但避免了崩溃.这是替换代码(使用 PureLayout):

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

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

We use auto layout constraints selectively, primarily to position labels in relation to editable field elements (UITextView, UITextField, typically). However, since implementing auto layout for these fields, we're seeing a nasty exception and crash whenever we're unloading views, deallocating, etc. The exceptions are happening as it's attempting to remove the constraints from a view before unloading it.

Our view/controller hierarchy is as such:

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**

Many times when the upper level table cells are being deallocated/replaced/reloaded, we see a huge exception and then crash as it's trying to deallocate/unload the view hierarchy within.

I've attempted to mitigate the crash by catching the exception (no help) and also by forcefully removing all of the constraints on the affected view and all of the subviews prior to deallocation/unload (in viewWillDisappear:) and it doesn't seem to help. I've even tried to remove these constraints one by one to see if there's one in particular that's causing the trouble but all of them are blowing up when we call removeConstraint: or removeConstraints: on a container in preparation for disappearing.

I'm baffled! Here's a snippet of our exception -- roughly about 3000 lines have been chopped out of it, so if you need more, just ask.

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 ******/

解决方案

I had an (extensive) conversation with an Apple engineer about this crash.

Here are the two most probable causes:

  1. You have an invalid constraint, such as view1.left = view2.left + 20 where view2 is unexpectedly nil, or has a multiplier of 0. Be sure to double (and triple) check your constraints to make sure that they are correct. Here are 2 examples of problematic constraints:

    // 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. You're hitting a bug in the internal Foundation auto layout engine related to accumulated loss of floating point precision. When you've crashed, the way you can know that this is the case is to search through the (large) exception log in the console for a very small (nearly zero) floating point number such as this:

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

(Search for e- in the console output to find small numbers like this.) This number (-7.45058e-08 in this case) represents the coefficient at this particular point in time while the internal engine is solving constraints. In this case, the number is supposed to be exactly 0, but due to the way the auto layout engine does calculations with floating point numbers it has become an extremely tiny negative number instead which blows everything up. If you can find such a number in the output, you know that you've hit this bug.

How can you work around this issue?

Changing the order that you add (activate) constraints can end up changing the order of calculations in the internal engine, which as a result can cause this issue to disappear as the math is done without any problematic loss of precision.

This issue seems to come up more frequently when you have changed the content compression resistance or content hugging priorities for views, so try commenting out any code that does that to see if it's causing this bug to happen, or re-ordering it to happen earlier or later in your constraint setup code.

More details about my specific case:

I ran into this crash on iOS. The steps to reproduce it were quite interesting:

  1. A view controller containing a table view was pushed on screen (in a navigation controller).
  2. The table view had to contain enough cells so they didn't all fit in the visible area, then it had to be scrolled to the last cell and then back up a bit (presumably, this was causing cells to be reused, which was triggering this issue).
  3. Then, when the view controller containing the table view was popped off the navigation stack, immediately after the pop animation completed the app would crash at the point where the view controller's view was removed from the view hierarchy.

After a lot of trial and error I was able to isolate the issue to one specific thing: setting the content compression resistance & hugging priorities for a UIImageView in each of the table view cells. In this case, the image view is being positioned using Auto Layout inside the cell, and to achieve the correct layout the image view needs to be exactly its intrinsic content size (the size of its image).

This was the problematic code:

// 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];

Removing the above code and replacing it with 2 constraints (at Required priority) to fix the width & height of the image view to the image's size achieved the same result, but avoided the crash. Here's the replacement code (using PureLayout):

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

I also found that just moving the problematic 4 lines to a different place in my constraint setup code resolved the issue, presumably because this changed the order of calculations sufficiently to prevent the problematic loss of precision.

这篇关于AutoLayout:removeFromSuperview/removeConstraints 抛出异常并严重崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆