【问题标题】:What is NSLayoutConstraint "UIView-Encapsulated-Layout-Height" and how should I go about forcing it to recalculate cleanly?什么是 NSLayoutConstraint“UIView-Encapsulated-Layout-Height”,我应该如何强制它干净地重新计算?
【发布时间】:2014-09-23 10:19:27
【问题描述】:

我有一个在 iOS 8 下运行的 UITableView,我正在使用情节提要中约束的自动单元格高度。

我的一个单元格包含一个 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 似乎是正确的。

更糟糕的是,虽然布局代码说它试图打破我的高度限制,但它没有 - 它继续重新计算单元格高度,并且一切都按照我的意愿绘制。

那么,NSLayoutConstraintUIView-Encapsulated-Layout-Height 是什么(我猜它是自动调整单元格大小的计算高度),我应该如何强制它干净地重新计算?

【问题讨论】:

  • 交叉发布到 Apple 开发者论坛:devforums.apple.com/thread/238803
  • 我通过以下方式解决了类似的问题,它适用于 iOS 7/8。 1) 将其中一个约束优先级降低到 750。我会尝试第一个或第二个 2) 在 awakeFromNib 集self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; 的单元子类中。我认为最初设置自动调整掩码会阻止添加最后一个约束。我在这里找到了这个解决方案:github.com/wordpress-mobile/WordPress-iOS/commit/…
  • @RogerNolan,有什么消息吗?我在 Interface Builder 中使用 Auto-layout 时发现了同样的问题。有些单元格会导致此问题,有些则不会。
  • 我不认为添加自动调整大小标记是一个好的解决方案。
  • @RogerNolan 您是否在执行这些布局更改之前在 IB 中生成此单元格?我一直在调试同样的问题,但我没有添加任何额外的约束。我设法通过从头开始重建视图来抑制警告,当我区分 2 个故事板文件时,唯一的区别是带有警告的版本在其定义中缺少 &lt;rect key="frame" x="0.0" y="0.0" width="600" height="110"/&gt; 行,这让我相信这是一个 IB 错误。至少我的还是这样。

标签: ios uitableview cocoa-touch autolayout ios-autolayout


【解决方案1】:

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

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

补充:虽然系统提供的约束是否正确可能存在争议,但与框架抗争是没有意义的。只需接受系统约束优先。如果您认为系统约束错误,请确保从委托中返回正确的 rowHeight。

【讨论】:

  • 这将与我想要的完全相反。 UIView-Encapsulated-Layout-Height 错误 - 它属于之前的布局。
  • 一旦确定高度,UITableView 就会添加UIView-Encapsulated-Layout-Height 约束。我根据 contentView 的systemLayoutSizeFittingSize 计算高度。在这里,UIView-Encapsulated-Layout-Height 无关紧要。然后,tableView 将 contentSize 显式设置为heightForRowAtIndexPath: 返回的值。在这种情况下,降低自定义约束的优先级是正确的,因为 tableView 约束必须在计算 rowHeights 之后优先。
  • @OrtwinGentz:仍然没有明白这一点降低我们的自定义约束的优先级是正确的,因为在计算 rowHeights 之后 tableView 约束必须优先。问题是,如果我不降低优先级,UIView-Encapsulated-Layout-Height 就错了……
  • 我坚持认为这是避免冲突而不是解决实际问题 - 尽管这仍然感觉像是 Apple 错误,但我认为这可能是正确的做法。尤其是考虑到 Apple 会重新计算此约束,并且在打印错误后一切都正确布局。
  • -1 对于这个答案,因为它假定添加的约束是正确的。在我的情况下添加的UIView-Encapsulated-Layout-Width 是错误的,但它似乎比我在运行时的显式约束更受欢迎。
【解决方案2】:

我有一个类似的场景:一个带有一个行单元格的表格视图,其中有几行 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

【讨论】:

  • 终于!这是一个正确的答案。在 WWDC 会议中,有人提到,如果你要使用自动行高调整,那么你应该设置一个估计的行高度,否则会发生不好的事情。 (是的,Apple 真的发生了令人讨厌的事情)
  • FWIW,添加估计对我来说根本没有任何区别。
  • 嘿。我又回到了同样的答案,并带着喜悦和希望去实施它。再一次,这对我来说没有任何区别:-)
  • tableView:estimatedHeightForRowAtIndexPath 返回的估计值:必须至少与单元格一样大。否则,表格的计算高度将小于实际高度,表格可能会向上滚动(例如 unwind segue 返回表格后)。不应使用 UITableViewAutomaticDimension。
  • 请注意,estimatedRowHeight 越低,最初调用 cellForRowAtIndexPath 的频率就越高。准确地说,tableview 高度除以estimatedRowHeight 次。在 12 英寸 iPad Pro 上,这可能是数以千计的数字,并且会影响数据源并导致严重延迟。
【解决方案3】:

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
            ])

【讨论】:

  • 美丽的解释。
  • 很棒的解释。
  • 是的,谢谢。为我工作。但我无法理解其背后的逻辑,也看不出在此错误发生时单元格的高度为 0。控制台中的日志显示类似 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fa661635050.height == 150 的内容。这意味着单元格的内容视图得到了它的高度。对我来说,没有高度为 0 的视图。
【解决方案4】:

我能够通过在约束中的一个值上指定priority 来消除警告,警告消息说它必须打破(低于"Will attempt to recover by breaking constraint")。看来只要我将优先级设置为大于49,警告就会消失。

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

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

到:

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

事实上,我可以为该约束的任何元素添加优先级,它会起作用。哪一个似乎并不重要。我的单元格最终达到了正确的高度,并且不显示警告。 Roger,以您的示例为例,尝试在 388 高度值约束之后添加 @500(例如 388@500)。

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

当您向视图发送一条fittingSize 消息时,最小尺寸 足够大以计算视图的内容。这是 视图希望尽可能小的优先级 那个计算。这是相当低的。一般不适合 在这个优先级上做一个约束。你想更高或 更低。

引用的fittingSize 消息的documentation 内容如下:

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

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

我还没有深入挖掘,但这似乎与问题所在有关。

【讨论】:

  • 那是杰夫。对我来说仍然像一个错误。苹果没有回应 rdar :-(
【解决方案5】:

我能够通过删除我在tableViewcellForRowAt 方法中的虚假cell.layoutIfNeeded() 来解决此错误。

【讨论】:

  • 是的,这也解决了我的问题。我正在做代码布局约束,所以我最初认为我可能会错过一些东西。谢谢
  • 同样的事情!谢谢!
【解决方案6】:

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

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

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

[tableView endUpdates];

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

【讨论】:

  • 我应该在我的问题中说明,我已经尝试过了,但它不起作用。我同意你对 UIView-Encapsulated-Layout-Height 的猜测
  • 无论如何都要获得赏金,至少要提交一个答案。似乎 SO 只会让它蒸发。
【解决方案7】:

另一种可能性:

如果您使用自动布局计算单元格高度(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)中在非常复杂的单元格布局中遇到了布局高度歧义。在我看来,由于一些内部舍入错误,内容有点压缩,如果不准备压缩约束,我就会变得模棱两可。所以不是在heightForRowAtIndexPath中返回UITableViewAutomaticDimension,而是返回[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
  • 当然是[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
  • 惊人地;删除分隔符是我的表的行为,所以谢谢。
【解决方案8】:

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

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

仅供参考,iOS 10 中不会出现此问题。

【讨论】:

  • 在 Swift 4.2 中:self.contentView.autoresizingMask = [.flexibleHeight]
  • 你把它放在哪里了?
【解决方案9】:

我在使用 UITableViewAutomaticDimension 并更改单元格内视图的高度约束时遇到此错误。

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

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

【讨论】:

  • 这条评论让我想到了我的问题。我有一个动态加载(增长和缩小)的图像单元格和一个估计RowHeight = 50 和rowHeight = UITableViewAutomaticDimension 的表格视图。即使 tableview 是正确的高度,我仍然在打破约束。原来分隔符的高度为 0.3333,这就是我在单元格中的图像大小限制被打破的原因。关闭分离器后一切都很好。感谢 Che 给了我要找的东西。
  • 在这种情况下,创建一个额外的约束,例如,bottomMargin >= view.bottomMargin+1@900。 AutoLayout 尝试容纳额外的 1 点,调整单元格的大小,由于分隔符高度而感到困惑,尝试打破/放松一些约束,找到 @900 的那个,然后丢弃它。您可以在没有警告的情况下获得所需的布局。
  • 拯救我的一天。反正几个小时
【解决方案10】:

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

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

单元格的高度正确,警告消失了。

【讨论】:

  • 谢谢,我今天遇到了类似的问题,唯一有效的解决方案是将最低优先级降低到 999。
  • 感谢准确的解决方案和自我解释的答案...拯救了我的一天...
  • 谢谢!...这对表格视图单元格也很好。
【解决方案11】:

我遇到了同样的问题。对我来说,错误是 0.5 像素。

2020-08-06 21:33:20.947369+0530 DemoNestedTableView[4181:384993] [LayoutConstraints] 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. 
(

        "<NSLayoutConstraint:0x600000a3abc0 UICollectionView:0x7fde0780c200.height == 326   (active)>",
        "<NSLayoutConstraint:0x600000a3ae40 V:|-(0)-[UICollectionView:0x7fde0780c200]   (active, names: '|':UITableViewCellContentView:0x7fde05e0cb10 )>",
        "<NSLayoutConstraint:0x600000a3af30 V:[UICollectionView:0x7fde0780c200]-(0)-|   (active, names: '|':UITableViewCellContentView:0x7fde05e0cb10 )>",
        "<NSLayoutConstraint:0x600000a2a4e0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fde05e0cb10.height == 326.5   (active)>"
    )

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000a3abc0 UICollectionView:0x7fde0780c200.height == 326   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

所以刚刚添加了这个并且它起作用了。

self.tableView.separatorStyle = .none

【讨论】:

    【解决方案12】:

    调整文本视图以适合其内容,并将高度约束常量更新为结果高度,为我修复了UIView-Encapsulated-Layout-Height 约束冲突,例如:

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

    【讨论】:

    • 你在哪里做的?在 layoutSubviews 中?
    【解决方案13】:

    花了几个小时来解决这个错误后,我终于找到了一个适合我的解决方案。我的主要问题是我为不同的单元格类型注册了多个笔尖,但特别允许一种单元格类型具有不同的大小(并非该单元格的所有实例都将具有相同的大小)。因此,当 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});

    希望这会有所帮助!

    【讨论】:

      【解决方案14】:

      没有人回答如何解决故事板中的案例。你有我的情况,top bottom constraintfixed height 优先级为 1000。这就是为什么在第一次加载时,由于表格视图单元格高度为 0,尝试将元素设置为固定高度,从而导致约束冲突. (我试图在 0 像素高度区域中插入 40 像素,因此编译器尝试丢弃 40 像素高度)。一旦tableview加载完毕,就不会生成(比如弹回tableview或者切换tab bar tab)

      所以将优先级从required @1000 更改为High @750Low @250。第一次加载,不考虑低优先级,然后调整layoutSubviews()中的所有约束

      【讨论】:

        【解决方案15】:

        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的优先级下设置最高优先级。

        【讨论】:

          【解决方案16】:

          我收到这样的消息:

          无法同时满足约束...
          ...
          ...
          ...
          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-| 之间的区别有点神秘。但是指定大小对我来说不是问题,它消除了日志,我不需要降低所需约束的优先级。

          【讨论】:

            【解决方案17】:

            设置这个view.translatesAutoresizingMaskIntoConstraints = NO; 应该可以解决这个问题。

            【讨论】:

            • 这对我来说非常有效,即使它不是完美的解决方案。
            【解决方案18】:

            就我而言,问题在于UITableViewCell 内的垂直UIStackView,它根据数据显示/隐藏行。我正在使用自动调整单元格大小,并且单元格本身始终正确显示,高度正确。只是日志中充满了关于UIView-Encapsulated-Layout-Height 的约束异常。

            我通过为UIStackView 的顶部和底部约束设置较低的优先级(999 而不是默认的1000)解决了这个问题。现在没有约束例外,表格的外观和行为都相同。

            【讨论】:

            • 天哪,我什么都试过了。这终于奏效了!
            【解决方案19】:

            如果您在带有 Xib/Storyboard 的调试控制台中出现 UIView-Encapsulated-Layout-Height 警告。

            你应该做这些事情:

            1. 转到Size Inspector页面
            2. 选中Row Height 复选框Automatic。记录Row Height Value
            3. tableView.estimatedRowHeight 设置为Value

            ?没有更多警告⚠️

            【讨论】:

            • 如果苹果不解决这个问题,它可能被称为修复。 (但我更喜欢称它为workaround?)
            【解决方案20】:

            在我的情况下,我确实设法通过将 Xcode 抱怨的约束的优先级设置为 750 来消除警告,但在删除并重新插入表格后单元格仍然使用错误的高度.

            问题来自这样一个事实,即对 beginUpdates()endUpdates()(在我进行删除和插入之间)方法的调用被封装在 UIView.animate(withDuration:) 动画块中,我想在其中控制持续时间更新表格时的动画。 删除对动画块的调用为我解决了这个问题。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-02-01
              • 2021-09-01
              • 2015-04-09
              • 2015-10-09
              • 2016-03-04
              • 2014-02-05
              • 2014-06-12
              • 2021-05-21
              相关资源
              最近更新 更多