NSAutoresizingMaskLayoutConstraint 的 UITableViewCell 舍入错误,但在 Storyboard 和 heightForRowAtIndexPath 中正

Posted

技术标签:

【中文标题】NSAutoresizingMaskLayoutConstraint 的 UITableViewCell 舍入错误,但在 Storyboard 和 heightForRowAtIndexPath 中正确设置了大小:【英文标题】:UITableViewCell rounding error with NSAutoresizingMaskLayoutConstraint, but size correctly set in Storyboard AND heightForRowAtIndexPath: 【发布时间】:2014-03-20 16:56:15 【问题描述】:

我正在尝试使用 AutoLayout 来配置我的表格视图单元格中的子视图,并且在理想情况下,我希望表格视图单元格尽可能高以包含所有子视图。但是,这似乎是不可能的,因为单元格的高度是在实际创建单元格之前确定的。

所以,现在,我只查看了我设置的约束并计算了包含所有内容所需的总高度,并将其在情节提要中设置为这个特定 TableViewCell 原型单元格的行高,我还在tableView:heightForRowAtIndexPath: 方法。

现在,我的表格视图单元格看起来像这样(来自故事板的屏幕截图):

有两个没有显示大小的约束,它们的大小都是 10(按钮顶部容器视图和滑块与中间标签之间的距离)。

从上到下会发生以下距离:

10(距离) 50(按钮高度) 20(距离) 20(标签高度) 10(距离) 30(滑块高度) 20(距离)

导致总高度为 160。

这正是我在这个单元的检查器中设置的:

但正如您从第一个屏幕截图中看到的那样,Interface builder 抱怨约束是冲突的(这就是它们显示为红色的原因)。如果我将高度设置为 161,则 IB 很满意,但这是错误的。

另外,如果我这样做了,由于约束冲突,我会在运行时遇到异常:

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:0x8d80240 V:[UISlider:0x8d81190(30)]>",
"<NSLayoutConstraint:0x8d833e0 V:[UILabel:0x8d83300(20)]>",
"<NSLayoutConstraint:0x8d83750 V:[UIButton:0x8d83600(50)]>",
"<NSLayoutConstraint:0x8d85050 V:|-(10)-[UIButton:0x8d83600]   (Names: '|':UITableViewCellContentView:0x8d80ef0 )>",
"<NSLayoutConstraint:0x8d851d0 V:[UILabel:0x8d83300]-(10)-[UISlider:0x8d81190]>",
"<NSLayoutConstraint:0x8d85200 V:[UISlider:0x8d81190]-(20)-|   (Names: '|':UITableViewCellContentView:0x8d80ef0 )>",
"<NSLayoutConstraint:0x8d85320 V:[UIButton:0x8d83600]-(20)-[UILabel:0x8d83300]>",
"<NSAutoresizingMaskLayoutConstraint:0x8d8b7a0 h=--& v=--& V:[UITableViewCellContentView:0x8d80ef0(161)]>"

)

最后一个是我唯一没有明确设置的,但它似乎是从情节提要中的行高设置生成的,因为我的tableView:heightForRowAtIndexPath: 方法返回 160:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

    if (indexPath.row == self.fetchedItemSetsController.fetchedObjects.count - 1)
    
        return 160;
    
    return 44;

(目前我总是希望最后一个单元格是大而特殊的)

所以,好吧,当我将行高设置为 160 时,IB 会抱怨,但当我将其设置为 161 时,运行时会抱怨。所以我尝试忽略 IB 并将情节提要中的行高设置为 160(如第二个屏幕截图所示)。在这种情况下,我会收到相同的错误消息,但差别很小:

"<NSLayoutConstraint:0x17809ce80 V:[UISlider:0x125613660(30)]>",
"<NSLayoutConstraint:0x1782814f0 V:[UILabel:0x125615100(20)]>",
"<NSLayoutConstraint:0x178281a90 V:[UIButton:0x125615b50(50)]>",
"<NSLayoutConstraint:0x178281b80 V:|-(10)-[UIButton:0x125615240]   (Names: '|':UITableViewCellContentView:0x178161980 )>",
"<NSLayoutConstraint:0x178281c20 UIButton:0x1256157b0.height == UIButton:0x125615240.height>",
"<NSLayoutConstraint:0x178281d10 UIButton:0x1256157b0.height == UIButton:0x125615980.height>",
"<NSLayoutConstraint:0x178281e00 V:[UILabel:0x125615100]-(10)-[UISlider:0x125613660]>",
"<NSLayoutConstraint:0x178281e50 V:[UISlider:0x125613660]-(20)-|   (Names: '|':UITableViewCellContentView:0x178161980 )>",
"<NSLayoutConstraint:0x178281f40 UIButton:0x125615980.height == UIButton:0x125615b50.height>",
"<NSLayoutConstraint:0x178282030 V:[UIButton:0x125615240]-(20)-[UILabel:0x125615100]>",
"<NSAutoresizingMaskLayoutConstraint:0x178283340 h=--& v=--& V:[UITableViewCellContentView:0x178161980(159.5)]>"

表格视图单元格的高度约束现在是 159.5 而不是 160。我之前也遇到过 160.5,具有相同的情节提要设置,但我不确定它来自哪里。

所以,起初我认为这只是 IB 中的一个显示错误,但现在它实际上似乎是在创建错误的约束。它使用 160.5(或 159.5)而不是我指定的 160 创建一个约束。 这是为什么呢?我该怎么办?

顺便说一句:单元格似乎显示正确,但我再次怀疑我能否看到 0.5 点的差异。主要是,我想摆脱异常,因为它们使调试更加困难,但我也想知道这里发生了什么。

更新: 159.5 不仅仅是因为我刚刚注意到的情节提要设置,它是情节提要设置和tableView:heightForRowAtIndexPath: 的返回值的奇怪组合。 以下是生成的 autoresizingmasklayoutconstraint 高度的几个示例,具体取决于情节提要高度 (sb) 设置和tableView:heightForRowAtIndexPath: 方法(方法)的返回值。在所有情况下,子视图的约束都应导致高度为 160,并且不会更改。

    160 (sb) & 160 (method): 159.5 (constraint) 161(sb)和 160(方法):159.5(约束) 160 (sb) & 161 (method): 160.5 (constraint) 162(sb)和 161(方法):162(约束) 161(sb)和 162(方法):161(约束) 162(sb)和 162(方法):162(约束)

所以我想,好吧,因为 (6.) 他们都同意,我将标签的高度设为 22,因此根据子视图的总高度也将是 162。结果:

6a。 162(sb)&162(方法),162(子视图总高度):161.5(约束)

什么?!

有什么想法吗?

更新 2 我提供了一个示例项目来重现问题on Github。

【问题讨论】:

有机会获得故事板和其他所需文件来重现它吗? 我看看能否在一个较小的示例项目中重现它。 我创建了一个小示例项目。只要这个问题存在,上传并保留它的最佳和最简单的方法是什么? 贴在github上。这允许其他人使用它并提交自己的更改。 @A-Live 我添加了一个有同样问题的示例项目。 【参考方案1】:

Separator 弄乱了 contentView 的高度,要么应该禁用它(如果需要,用自定义的替换它)以便 contentView 高度与 cell 的高度匹配,或者约束应该考虑到contentView 的高度可能与cell 的高度不匹配,可以将其更改为更灵活。两者都做会更好。

【讨论】:

啊,分隔符是罪魁祸首!我真的不明白为什么约束,虽然计算为匹配内容高度在运行时不适合,但分隔符带走了部分内容视图的可用高度是有道理的。不过,对于界面构建器和委托调用中单元格高度的不同设置,约束的行为仍然很奇怪。可以使用@matt 的答案动态计算单元格的正确高度,而不是使约束更加灵活。 SB 和委托值的不同组合的“奇怪”结果可能是由自动尝试解决布局问题引起的。尽管委托方法的值应该具有更高的优先级,但必须从 SB 预先分析约束(因为这是您配置它们的唯一地方),并且那里的不同值可能会给出不同的分析结果。对于那些不知道实现的人来说,这样的自动修复过程可能是相当随机的,但是当它发生时我们会发出警告,所以我想这很好。 @A-Live 分隔符!!!你刚刚让一个巨大的灯泡出现在我的脑海里。非常感谢。【参考方案2】:

现在您已将示例上传到 github,我可以看到约束实际上是过度确定的。尤其是“这等于那个”太多了。在垂直间距/高度方面。我简化了约束,并且能够将您的代码更改为:

-(CGFloat)tableView:(UITableView *)tableView 
    heightForRowAtIndexPath:(NSIndexPath *)indexPath

    if (indexPath.row == 0)
    
        return 44;
     // else
    UITableViewCell* cell = 
        [tableView dequeueReusableCellWithIdentifier:@"LargeCell"];
    CGSize sz = 
        [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingExpandedSize];
    return sz.height;

如果我没记错的话,这正是您所追求的,即让约束本身决定单元格的高度。

【讨论】:

感谢您发布有关如何正确使用systemLayoutFittingSize 的完整示例,这是我在查看其他一些问题和您的书示例时遇到的问题。我接受了@A-Live 的回答,因为它更好地解释了原因。不过,您的方法更好地解释了如何。感谢您的有趣讨论。 @JoachimKurz 在我看来(这不关我的事),您接受 A-live 的回答是完全正确的。我没有提到分隔符,因为我没有想到! (确实,我记下了我的“回旋余地”答案,因为我和你一样觉得这并不令人满意。)你真的把我们带到了一条迷人的道路上,我学到了很多东西。 @JoachimKurz 这太棒了。在github.com/mattneub/Programming-ios-Book-Examples/blob/master/… 中,我在约束生成的单元格高度上加了 1,但不明白为什么。现在我愿意!是分隔符!! 为此 +1,虽然我发现我不需要使用 UILayoutFittingExpandedSize - 您可以传递自己的大小并使用允许您传递布局优先级的变体:systemLayoutSizeFittingSize: size withHorizontalFittingPriority: UILayoutPriorityRequired verticalFittingPriority: 1。我错过了一件非常重要的事情,今天才在这个答案中注意到,你应该使用单元格的 contentView,而不是单元格本身。我们总是在返回的高度中得到 0.5pt 太高,这导致了很多模棱两可的布局。 3 个月的疼痛消失了,谢谢!

以上是关于NSAutoresizingMaskLayoutConstraint 的 UITableViewCell 舍入错误,但在 Storyboard 和 heightForRowAtIndexPath 中正的主要内容,如果未能解决你的问题,请参考以下文章