什么是NSLayoutConstraint“ UIView-Encapsulated-Layout-Height”,我应该如何强制其重新进行干净的计算?


262

UITableView在iOS 8下运行,并且正在使用情节提要中的自动单元高度。

我的一个单元格包含一个UITextView,我需要它根据用户输入进行收缩和扩展-点击以收缩/扩展文本。

我通过向文本视图添加运行时约束并响应用户事件来更改约束的常量来做到这一点:

-(void)collapse:(BOOL)collapse; {

    _collapsed = collapse;

    if(collapse)
        [_collapsedtextHeightConstraint setConstant: kCollapsedHeight]; // 70.0
    else
        [_collapsedtextHeightConstraint setConstant: [self idealCellHeightToShowFullText]];

    [self setNeedsUpdateConstraints];

}

每当执行此操作时,我都会将其包装在tableView更新中并调用[tableView setNeedsUpdateConstraints]

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView setNeedsUpdateConstraints];
// I have also tried 
// [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
// with exactly the same results.

[tableView endUpdates];

当我这样做时,我的单元格确实会展开(并在执行过程中设置动画),但是我收到了约束警告:

2014-07-31 13:29:51.792 OneFlatEarth[5505:730175] Unable to simultaneously satisfy constraints.

Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 

(

    "<NSLayoutConstraint:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>",

    "<NSLayoutConstraint:0x7f94dced2260 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']-(15)-|   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced2350 V:|-(6)-[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced6480 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f94de5773a0(91)]>"
 )

Will attempt to recover by breaking constraint 

<NSLayoutConstraint:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>

388是我计算的高度,其他约束UITextView是我的Xcode / IB。

最后一个困扰我-我猜这UIView-Encapsulated-Layout-Height是单元格第一次渲染时计算出的高度-(我将UITextView高度设置为> = 70.0),但是这种派生的约束然后推翻了更新了用户cnstraint。

更糟糕的是,尽管布局代码表示它正在尝试突破我的高度限制,但事实并非如此-它继续重新计算像元高度,一切都按照我的意愿绘制。

那么,什么是NSLayoutConstraint UIView-Encapsulated-Layout-Height(我猜这是自动计算单元大小的计算高度),我应该如何强迫它重新进行干净的计算呢?


3
交叉发布到Apple开发人员论坛:devforums.apple.com/thread/238803
Rog,

3
我以以下方式解决了类似的问题,它可在iOS 7/8上运行。1)将约束优先级之一降低到750。我将尝试1st或2nd 2)在aakeakeFromNib set的单元格子类中self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;。我认为最初设置自动调整大小蒙版可阻止添加最后一个约束。我在这里找到了这个解决方案:github.com/wordpress-mobile/WordPress-iOS/commit/…–
Jesse

3
@RogerNolan,有什么消息吗?在Interface Builder中使用自动布局时,我发现了相同的问题。有些单元格会导致此问题,有些则不会。
Orkenstein 2014年

3
我认为添加自动调整大小标记不是一个好的解决方案。
Rog 2015年

1
@RogerNolan是否在执行这些布局更改之前在IB中生成此单元格?我一直在调试相同的问题,但没有添加任何额外的约束。我设法通过从头开始重建视图来抑制警告,并且当我对两个情节提要文件进行差异化处理时,唯一的区别是带有警告的版本缺少<rect key="frame" x="0.0" y="0.0" width="600" height="110"/>其定义中的行,从而使我认为这是一个IB错误。 。至少我的还是。
Ell Neal 2015年

Answers:


301

尝试将您的优先级降低_collapsedtextHeightConstraint到999。这样,系统提供的UIView-Encapsulated-Layout-Height约束始终优先。

它基于您返回的内容 -tableView:heightForRowAtIndexPath:。确保返回正确的值和您自己的约束,并且生成的约束应相同。您自己的约束的较低优先级只是暂时需要的,以防止在播放折叠/展开动画时发生冲突。


74
那将与我想要的完全相反。UIView-Encapsulated-Layout-Height错误-它属于先前的布局。
Rog 2014年

7
UIView-Encapsulated-Layout-Height约束由添加的UITableView一次高度被确定。我根据systemLayoutSizeFittingSizecontentView 的高度计算高度。在这里,UIView-Encapsulated-Layout-Height没关系。然后,tableView将contentSize显式设置为所返回的值heightForRowAtIndexPath:。在这种情况下,降低自定义约束的优先级是正确的,因为tableView约束必须在计算rowHeights之后优先。
Ortwin Gentz 2014年

8
@OrtwinGentz:仍然不能正确地降低我们的自定义约束的优先级,因为tableView约束在计算rowHeights之后必须优先。问题是,UIView-Encapsulated-Layout-Height如果我不降低优先级,那就错了……
测试

38
我坚持认为避免冲突不会解决实际问题-尽管由于这仍然像是Apple Bug,我认为这可能是正确的选择。特别是考虑到Apple重新计算了此约束,并且在错误打印后一切都正确布置了。
Rog 2015年

9
该答案为-1,因为它假定要添加的约束是正确的。UIView-Encapsulated-Layout-Width在我看来,添加的内容是错误的,但是在运行时它似乎比我的显式约束更可取。
2015年

68

我有一个类似的场景:一个带有一个行单元格的表视图,其中有几行UILabel对象。我正在使用iOS 8和自动版式。

当我旋转时,我得到了错误的系统计算行高度(43.5比实际高度小得多)。看起来像:

"<NSLayoutConstraint:0x7bc2b2c0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7bc37f30(43.5)]>"

这不仅仅是警告。我的表格视图单元格的布局很糟糕-所有文本都重叠在一条文本行上。

令我惊讶的是,以下代码行神奇地“解决”了我的问题(自动布局没有任何提示,并且在屏幕上得到了我期望的结果):

myTableView.estimatedRowHeight = 2.0; // any number but 2.0 is the smallest one that works

有无此行:

myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem

8
最后!这是正确的答案。在WWDC会话中,曾提到过,如果您要使用自动行高调整大小,则应设置estimateRowHeight或发生坏事。(是的,Apple确实发生了令人讨厌的事情)
Abdalrahman Shatou 2015年

50
FWIW,加上估算对我来说丝毫没有影响。
约翰·

3
嘿。我又回到了同样的答案,并带着喜悦和希望去实现它。再次,对我来说没有什么区别:-)
Benjohn

3
tableView:estimatedHeightForRowAtIndexPath:返回的估算值必须至少与单元格一样大。否则,计算得出的桌子高度将小于实际高度,并且桌子可能会向上滚动(例如,在放宽缝返回桌子之后)。不应使用UITableViewAutomaticDimension。
马特

1
请注意,越低,最初将被estimatedRowHeight频繁cellForRowAtIndexPath调用。确切地说,表视图高度除以估计的行高度的倍数。在12英寸iPad Pro上,这可能是成千上万的数字,这将影响数据源,并可能导致严重的延迟。
Mojo66'2013/

32

通过在警告消息表示必须中断的约束中指定一个值的优先级,可以使警告消失(在下方"Will attempt to recover by breaking constraint")。看来,只要我将优先级设置为大于49,警告就会消失。

对我来说,这意味着更改我的约束,警告说它试图打破:

@"V:|[contentLabel]-[quoteeLabel]|"

至:

@"V:|-0@500-[contentLabel]-[quoteeLabel]|"

实际上,我可以为该约束的任何元素添加一个优先级,它将起作用。似乎哪一个都不重要。我的牢房最终达到适当的高度,并且不显示警告。罗杰(Roger),例如,尝试@500388高度值约束之后添加(例如388@500)。

我不完全确定为什么会这样,但是我做了一些调查。在NSLayoutPriority枚举中,似乎NSLayoutPriorityFittingSizeCompression优先级为50。该优先级的文档说:

当您向视图发送fittingSize消息时,将计算出足以容纳视图内容的最小大小。这是视图在计算中希望尽可能小的优先级。相当低。通常不恰当地对此优先级进行约束。您想要更高或更低。

所引用消息的文档fittingSize

满足其约束的视图的最小大小。(只读)

AppKit将此属性设置为可用于视图的最佳大小,同时考虑到它及其子视图所具有的所有约束,并满足使视图尽可能小的偏好。此属性中的大小值永远不会为负。

我没有超出这个范围,但是这确实与问题所在有关。


杰夫。在我看来仍然像个虫子。苹果没有对rdar做出回应:-(
Rog


12

99.9%的时间,在使用自定义单元格或标题时,所有的冲突都UITableViews在表第一次加载时发生。加载后,通常通常不会再看到冲突。

发生这种情况是因为大多数开发人员通常使用某种固定高度或锚点约束来在单元格/标题中布置元素。之所以会发生冲突,是因为在第UITableView一个加载/布置时,它将其单元格的高度设置为0。这显然与您自己的约束冲突。要解决此问题,只需将任何固定的高度限制设置为较低的优先级(.defaultHigh)。仔细阅读控制台消息,看看布局系统决定打破哪个约束。通常这是需要更改其优先级的那个。您可以这样更改优先级:

let companyNameTopConstraint = companyNameLabel.topAnchor.constraint(equalTo: companyImageView.bottomAnchor, constant: 15)
    companyNameTopConstraint.priority = .defaultHigh

NSLayoutConstraint.activate([
            companyNameTopConstraint,
           the rest of your constraints here
            ])

1
美丽的解释。
格伦

7

与其通知表视图更新其约束,不如尝试重新加载单元格:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView reloadRowsAtIndexPaths:@[[tableView indexPathForCell:_briefCell]] withRowAnimation:UITableViewRowAnimationNone];

[tableView endUpdates];

UIView-Encapsulated-Layout-Height 可能是初始加载期间基于该单元当时的约束为该单元计算的表格视图的高度。


我应该在我的问题中说过,我已经尝试过了,但是不起作用。我同意您对UIView-Encapsulated-Layout-Height的猜测
Rog,2014年

6
无论如何,至少要提交答案才能获得赏金。好像SO只会让它蒸发掉,否则。
Rog 2014年

6

另一种可能性:

如果您使用自动布局来计算像元高度(contentView的高度,大部分时间如下),并且如果您具有uitableview分隔符,则需要添加分隔符高度,以返回像元高度。一旦获得正确的高度,就不会发出自动布局警告。

- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
   [sizingCell setNeedsLayout];
   [sizingCell layoutIfNeeded];
   CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
   return size.height; // should + 1 here if my uitableviewseparatorstyle is not none

}

在只有某些模拟器(iPad 6 Plus)才能在相当复杂的单元布局中获得布局高度歧义的情况下,这确实对我有帮助。在我看来,由于某些内部舍入错误,内容受到了一些挤压,如果不准备挤压约束,那我会感到模棱两可。因此UITableViewAutomaticDimensionheightForRowAtIndexPath我没有返回,而是返回 [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
Leo

我的意思是[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
Leo

惊人地 删除分隔符是我的桌子的行为,所以谢谢。
royalmurder

5

正如Jesse在有问题的评论中提到的那样,这对我有用:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

仅供参考,在iOS 10中不会发生此问题。


2
在Swift 4.2中:self.contentView.autoresizingMask = [.flexibleHeight]
airowe

4

使用UITableViewAutomaticDimension并更改单元格内视图的高度约束时出现此错误。

我终于发现这是由于约束常数值没有四舍五入到最接近的整数。

let neededHeight = width / ratio // This is a CGFloat like 133.2353
constraintPictureHeight.constant = neededHeight // Causes constraint error
constraintPictureHeight.constant = ceil(neededHeight) // All good!

2
这是使我陷入困境的评论。我有一个可以动态加载(增长和收缩)的图像单元,并带有一个估计值为RowHeight = 50和rowHeight = UITableViewAutomaticDimension的表格视图。即使tableview是正确的高度,我仍然在打破约束。事实证明,分隔符的高度为0.3333,这就是要破坏单元格中图像大小约束的原因。关闭隔板后,一切都很好。谢谢Che给我想要的东西。
migs647

1
在这种情况下,请创建一个额外的约束,例如,bottomMargin> = view.bottomMargin+1@900。AutoLayout尝试容纳额外的1个点,调整单元格的大小,由于分隔符的高度而感到困惑,尝试打破/放松一些约束,找到一个@ 900,然后丢弃该约束。您会得到想要的布局而不会发出警告。
安东

保存我的一天。无论如何,还是几个小时
Anton Tropashko

1

调整文本视图的大小以适合其内容,并将高度约束常量更新为所得的高度,从而UIView-Encapsulated-Layout-Height为我解决了约束冲突,例如:

[self.textView sizeToFit];
self.textViewHeightConstraint.constant = self.textView.frame.size.height;

你在哪里做的?在layoutSubviews中?
stickj 2016年

1

在花了几个小时解决这个错误之后,我终于找到了对我有用的解决方案。我的主要问题是我为不同的细胞类型注册了多个笔尖,但是专门允许一种细胞类型具有不同的大小(并非该细胞的所有实例都将具有相同的大小)。因此,当tableview试图使该类型的单元出队时,出现了问题,并且碰巧具有不同的高度。我通过设置解决了

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; self.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

每当单元具有其数据来计算其大小时。我认为它可以在

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

就像是

cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; cell.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

希望这可以帮助!


0

TableView从委托获取indexPath上单元格的高度。然后从获取单元格cellForRowAtIndexPath

top (10@1000)
    cell
bottom (0@1000)

如果cell.contentView.height:0 // <->(UIView-Encapsulated-Layout-Height:0 @ 1000)top(10 @ 1000)与(UIView-Encapsulated-Layout-Height:0 @ 1000)冲突,

因为它们的优先级等于1000。我们需要在UIView-Encapsulated-Layout-Height的优先级下设置最高优先级。


0

我收到这样的消息:

无法同时满足约束条件……
...
...
...
NSLayoutConstraint:0x7fe74bdf7e50'UIView-Encapsulated-Layout-Height'V:[UITableViewCellContentView:0x7fe75330c5c0(21.5)]
...
...
将尝试通过以下方式恢复打破约束NSLayoutConstraint:0x7fe0f9b200c0 UITableViewCellContentView:0x7fe0f9b1e090.bottomMargin == UILabel:0x7fe0f9b1e970.bottom

我使用的是自定义的UITableViewCellUITableViewAutomaticDimension该高度。而且我还实施了estimatedHeightForRowAtIndex:方法。

给我带来问题的约束看起来像这样

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[title]-|" options:0 metrics:nil views:views];

将约束更改为此可以解决问题,但是像另一个答案一样,我觉得这是不正确的,因为它降低了我需要的约束的优先级:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6@999-[title]-6@999-|" options:0 metrics:nil views:views];

但是,我注意到的是,如果我实际上只是删除了优先级,那么它也可以工作,并且我没有得到破坏约束日志:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6-[title]-6-|" options:0 metrics:nil views:views];

|-6-[title]-6-|和之间的区别是个谜|-[title-|。但是,指定大小对于我来说不是问题,它摆脱了日志,并且我不需要降低所需约束的优先级。


0

我在集合视图单元格中遇到了类似的问题。

我通过将链接到单元格底部的最终约束的优先级降低到999(该约束是从视图的顶部到底部的最后一个约束-最终决定了它的高度)来解决此问题。

牢房的高度正确,警告消失了。


-1

设置此选项view.translatesAutoresizingMaskIntoConstraints = NO;可以解决此问题。


即使不是完美的解决方案,这对我来说也非常有效。
恩卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.