我们有选择地使用自动布局约束,主要是为了定位与可编辑字段元素相关的标签(通常是UITextView,UITextField)。但是,自从为这些字段实现自动布局以来,每当我们卸载视图,解除分配等时,我们都会看到一个令人讨厌的异常并崩溃。异常正在发生,因为它试图在卸载视图之前从视图中删除约束。
我们的视图/控制器层次结构是这样的:
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**
很多时候,当高级表格单元格被释放/替换/重新加载时,我们会看到一个巨大的异常,然后在尝试解除分配/卸载视图层次结构时崩溃。
我试图通过捕获异常(没有帮助)并通过在释放/卸载之前强行删除受影响的视图和所有子视图的所有约束来缓解崩溃(在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 ******/
答案 0 :(得分:88)
我和一位苹果工程师就此次事故进行了(广泛的)对话。
以下是两个最可能的原因:
您有一个无效的约束,例如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];
您在内部基础自动布局引擎中遇到了与累积浮点精度损失相关的错误。当你崩溃时,你可以知道这种情况的方法是在控制台中搜索一个非常小(几乎为零)的浮点数,例如:
<505:-7.45058e-08>*PWPlotLegendEntryView:0x600000582be0.Height{id: 34609} +
(在控制台输出中搜索e-
以查找这样的小数字。)此数字(在这种情况下为-7.45058e-08
)表示内部引擎解决时此特定时间点的系数限制。在这种情况下,数字应该恰好为0,但由于自动布局引擎使用浮点数进行计算的方式,它已成为一个非常小的负数,而不是将所有内容都搞砸了。如果你能在输出中找到这样的数字,你知道你已经遇到了这个错误。
你如何解决这个问题?
更改添加(激活)约束的顺序最终可能会更改内部引擎中的计算顺序,从而导致此问题在数学运算完成时消失,而不会出现任何问题精度损失。
当您更改了视图的内容压缩阻力或内容拥抱优先级时,此问题似乎更频繁出现,因此请尝试注释掉任何执行此操作的代码,以查看是否导致此错误发生,或重新排序在约束设置代码中更早或更晚发生。
有关我具体案例的更多详情:
我在iOS上遇到了这次崩溃。重现它的步骤非常有趣:
经过大量的反复试验后,我能够将问题分解为一个特定的问题:设置内容压缩阻力&amp;在每个表格视图单元格中拥抱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];
删除上述代码并将其替换为2个约束(所需优先级)以修复宽度&amp;图像视图的高度与图像的大小达到了相同的结果,但避免了崩溃。这是替换代码(使用PureLayout):
[self.imageView autoSetDimensionsToSize:self.imageView.image.size];
我还发现只是将有问题的4行移动到我的约束设置代码中的不同位置解决了这个问题,大概是因为这改变了计算的顺序,足以防止有问题的精度损失。
答案 1 :(得分:14)
使用自动布局的代码可以在主线程上运行,但是在后台运行并使用您的视图(可能是间接的)的块之一可能会对视图或其所有者之类的视图保持强引用controller(这是Objective-C块的默认行为)。当这样的块在后台队列上运行并解除分配时,它捕获的强引用将在同一队列中释放,您可能会面对一个众所周知的deallocation problem。
在视图控制器中,确保在所有不需要需要强引用的块中使用对self
的弱引用(并且可以在后台运行)。您可以在块之前声明如下:__weak typeof(self) weakSelf = self;
- 并在块内使用weakSelf
。
同样适用于包含对您的观点的引用的任何局部变量 - 确保将其值捕获为弱引用。
在我的工作中,当隐藏视图参与布局时,我在 iOS 6 上遇到了类似的问题。从层次结构中删除视图(-[UIView removeFromSuperview]
)而不是将hidden
属性设置为YES为我解决了问题。
答案 2 :(得分:3)
有同样的问题,通过在IB中一次删除一个约束来解决它,直到崩溃解决了。这使它缩小到违规的约束。然后我恢复了所述约束,但撤消了这些项目:
您可能很幸运,能够轻松解决您的AL问题。
答案 3 :(得分:2)
让@ smileyborg的精彩答案更具可操作性:
如果您有任何可能受浮点精度问题影响的乘数约束,就会发生这种情况。
要解决:
multiplier=
)。简单的方法2是输入你想要的数字和计算器,然后关闭尾数中的低精度位,直到该值与计算器底部的舍入小数值相匹配。
答案 4 :(得分:2)
对于在任何iOS版本中遇到此问题的任何人&gt; 8.0,Apple docs声明在NSLayoutConstraint上使用“active”属性而不是在UIView上使用removeConstraint / addConstraint函数。 Apple Docs addConstraint reference
答案 5 :(得分:1)
在我的例子中,它是一个比例宽度约束,具有8:9乘数。我将其改为7:9并且有效。
BTW查找约束的最简单方法是从视图控制器开始删除视图。使用二进制算法:)删除一半视图,然后一半使应用程序崩溃,等等。
答案 6 :(得分:1)
对我来说,问题是我在设置我的UICollectionViewCell的属性时调用了dequeueReusableCellWithReuseIdentifier之后删除了一个适合我的约束。解决方案是改为:
[_myUICollectionViewCell setNeedsUpdateConstraints];
并覆盖:
-(void)updateConstraints
并在那里搞乱。似乎你不能在你喜欢时删除约束。
答案 7 :(得分:0)
我在OSX Mavericks下遇到了同样的错误,我正在开发一个OSX应用程序,但与其他答案不同,我绝对没有任何其他线程与UI对象交互,并且有问题的视图层次结构也是绝对可见的。我也没有使用过块。当我删除NSTextField上的垂直约束时,问题就消失了。
FWIW有问题的视图,从其超级视图中删除会导致&#34;内部错误。找不到传入头的传出行头&#34;错误是许多侧面板控件之一,它们一起呈现主视图中可以剪切,复制,创建等对象的属性。这意味着用户可以非常快速地将新对象粘贴到主视图中,这意味着侧面-panel的控件正在被销毁,新的控件也在迅速创建。当然,对于主线程中的所有内容,这不应该有所作为,但似乎是。
导致问题的确切约束是
[self addConstraint: [NSLayoutConstraint constraintWithItem:控制 属性:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:其它 属性:NSLayoutAttributeHeight 乘数:1.4 常数:0.0]];
其中控制是(可编辑的)NSTextField导致问题,而其他&#39;是另一个(不可编辑的)NSTextField标签。
答案 8 :(得分:0)
我在使用MZFormSheetController
pod:https://github.com/m1entus/MZFormSheetController/issues/78
此代码崩溃:
[formSheetController.view addSubview:self.sharePanel];
// ...
[self.sharePanel removeFromSuperview]; // <-- CRASHES HERE
我的解决方案非常奇怪,但确实有效:
[self.sharePanel removeFromSuperview]; // <-- This line helps to avoid crash
[formSheetController.view addSubview:self.sharePanel];
// ...
[self.sharePanel removeFromSuperview];
这是sharePanel
财产声明:
@property (weak, nonatomic) IBOutlet UIView *sharePanel;
答案 9 :(得分:0)
当我使用nil参数调用removeConstraints时,我遇到了这个崩溃。
答案 10 :(得分:0)
当我在wAnyhAny模式下仍然缺少约束时,我遇到了这个崩溃,修复此问题删除了错误。
答案 11 :(得分:0)
正如这个帖子中的其他答案所表明的那样,这在某种程度上是一个无效的自动布局/约束问题,尽管它看起来非常挑剔“无效”。
幸运的是,自从我上一次提交以来,我没有做过很多改变,并且能够追踪违规的变化。对于我来说,我看到10个水平UIImageView
具有相同的宽度和固定的2:3宽高比是问题。
在离开包含此图像行的UIViewController
后,似乎只发生了崩溃。每个UIImageView
都设置为UIViewContentModeScaleAspectFill
。删除此内容模式更改(在设置UIImage
之前完成)似乎解决了我的问题,但这不是一个可接受的解决方案。我最终删除了宽高比限制,只为每个图像使用固定的宽度和高度。
为什么这会导致我的应用程序崩溃我不知道......在运行iOS 7.1.2的iPhone 4上,崩溃也可以仅重现。我试图在运行iOS 9.1的iPhone 4s模拟器上重现同样的崩溃而没有成功。在运行iOS 9.1的运行iPhone 5上运行时也不会崩溃。
希望能帮到那里的人
答案 12 :(得分:0)
根据Apple文档:
在针对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
}
}