ios 自我调整大小的 UITableViewCell 内的多个 UILabel

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25947146/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:39:17  来源:igfitidea点击:

Multiple UILabels inside a self sizing UITableViewCell

iosuitableviewswiftautolayoutios8

提问by Isuru

In this iOS 8 app I'm creating, I have a tableview and I need them to be self resizing. I implemented it using Auto Layout and it works. Almost. Here's how it looks now.

在我创建的这个 iOS 8 应用程序中,我有一个 tableview,我需要它们自我调整大小。我使用自动布局实现了它并且它有效。几乎。这是它现在的样子。

enter image description here

在此处输入图片说明

There are 3 labels inside a cell. Main label which has the lorem ipsum text. Subtitle which has the string of numbers (Those are two separate labels. Might be confusing because they have the same color.) Then the third label with the small black text.

一个单元格中有 3 个标签。具有 lorem ipsum 文本的主标签。带有数字字符串的副标题(这是两个单独的标签。可能会混淆,因为它们具有相同的颜色。)然后是带有黑色小文本的第三个标签。

The first label resized itself correctly with no problem and the second label moves up and down accordingly. But the problem is with the third small label. As you can see, its not resizing itself to fit all the text.

第一个标签正确调整大小,没有问题,第二个标签相应地上下移动。但问题在于第三个小标签。如您所见,它不会调整自身大小以适应所有文本。

Now there's a weird thing happening. I turn it landscape and here's it is.

现在发生了一件奇怪的事情。我把它变成风景,就是这样。

enter image description here

在此处输入图片说明

Since there is space the label is displaying the entire text its supposed to. Fine. Then I turn it back to portrait.

由于有空间,标签显示了它应该显示的整个文本。美好的。然后我把它转回肖像。

enter image description here

在此处输入图片说明

Now the small label has resized itself to fit all its text but it overflows the cells boundaries. I tried making the cell bigger but it didn't work. Since this is self sizing cells, I don't think that's the correct way even.

现在小标签已经调整大小以适应其所有文本,但它溢出了单元格边界。我尝试将单元格变大,但没有用。由于这是自我调整细胞大小,我认为这甚至不是正确的方法。

I'm not getting any errors or even warning on my auto layout constraints either.

我的自动布局约束也没有收到任何错误甚至警告。

enter image description here

在此处输入图片说明

I have set these two lines of code in the viewDidLoad()method.

我已经在viewDidLoad()方法中设置了这两行代码。

tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension

Can anyone please tell me what I might be doing wrong here?

谁能告诉我我在这里可能做错了什么?

Since its difficult to answer just by looking at images and I don't have any more code to post beside the above snippet, I uploaded a runnable Xcode project demonstrating the issue here. (There are 2 custom cells. Basically its the same cell just the height is increased in the second one.)

由于仅通过查看图像很难回答,并且除了上述代码段之外我没有更多代码可以发布,因此我上传了一个可运行的 Xcode 项目,在此处演示了该问题。(有 2 个自定义单元格。基本上它是相同的单元格,只是在第二个单元格中增加了高度。)

I've been fiddling with auto layout constraints but I can't seem to get this working. Any help would be appreciated.

我一直在摆弄自动布局约束,但我似乎无法让它工作。任何帮助,将不胜感激。

Thank you.

谢谢你。



UPDATE:

更新:

With the help of this tutorialI found some helpful pointers. According to it, each subview should have constraints that pin all its sides and there should be constraints that goes from top to bottom which helps auto layout to calculate the height of the cell. In my original post, I had vertical spaces between each label so I think that's the reason auto layout couldn't calculate the proper height.

在本教程的帮助下,我找到了一些有用的提示。根据它,每个子视图都应该有约束它的所有边,并且应该有从上到下的约束,这有助于自动布局计算单元格的高度。在我原来的帖子中,我在每个标签之间都有垂直空间,所以我认为这就是自动布局无法计算正确高度的原因。

So I made some changes.

所以我做了一些改变。

  • I reduced the vertical space between labels to 0 and set the Vertical space constraints between top and middle labels and middle and bottom labels.
  • I added leading, top, trailing constraints to the top label.
  • Leading and trailing to the middle label.
  • Leading, bottom, trailing to the bottom label.
  • 我将标签之间的垂直空间减少到 0,并设置了顶部和中间标签以及中间和底部标签之间的垂直空间约束。
  • 我在顶部标签中添加了前导、顶部和尾部约束。
  • 引导和拖尾到中间标签。
  • 前导、底部、尾随到底部标签。

Now here's another weird part. When I first run it, the bottom label cropping issue is still there.

现在这是另一个奇怪的部分。当我第一次运行它时,底部标签裁剪问题仍然存在。

enter image description here

在此处输入图片说明

But if I rotate the device to landscape and turn it back to portrait, all the all the cells are resized properly to fit both labels!

但是,如果我将设备旋转为横向并将其转回纵向,则所有单元格都会正确调整大小以适合两个标签!

enter image description here

在此处输入图片说明

Still can't figure out why this doesn't happen at first though. Updated Xcode project is here.

仍然无法弄清楚为什么一开始不会发生这种情况。更新的 Xcode 项目在这里

回答by smileyborg

The issue here is with the multi-line labels' preferredMaxLayoutWidthproperty. This is the property that tells the label when it should word wrap. It must be set correctly in order for each label's intrinsicContentSizeto have the correct height, which is ultimately what Auto Layout will be using to determine the cell's height.

这里的问题是多行标签的preferredMaxLayoutWidth属性。这是告诉标签何时应该自动换行的属性。必须正确设置它才能使每个标签intrinsicContentSize具有正确的高度,这最终是 Auto Layout 将用来确定单元格高度的。

Xcode 6 Interface Builder introduced a new option to have this property set to Automatic. Unfortunately, there are some serious bugs (as of Xcode 6.2/iOS 8.2) where this is not set correctly/automatically when loading a cell from a nib or Storyboard.

Xcode 6 Interface Builder 引入了一个新选项来将此属性设置为Automatic。不幸的是,有一些严重的错误(从 Xcode 6.2/iOS 8.2 开始),当从笔尖或故事板加载单元格时,这些错误没有正确/自动设置。

In order to work around this bug, we need to have the preferredMaxLayoutWidthset to be exactly equal to the final width of the label once it is displayed in the table view. Effectively, we want to do the following before returning the cell from tableView:cellForRowAtIndexPath::

为了解决这个错误,我们需要让preferredMaxLayoutWidth标签在表格视图中显示后与标签的最终宽度完全相等。实际上,我们希望在从 返回单元格之前执行以下操作tableView:cellForRowAtIndexPath:

cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)

The reason that just adding this code alone doesn't work is because when these 3 lines of code execute in tableView:cellForRowAtIndexPath:, we are using the width of each label to set the preferredMaxLayoutWidth-- however, if you check the width of the labels at this point in time, the label width is totally different from what it will end up being once the cell is displayed and its subviews have been laid out.

究其原因,只是单独添加该代码不起作用是因为,当这些代码3行执行的tableView:cellForRowAtIndexPath:,我们使用的每个标签的宽度来设置preferredMaxLayoutWidth-但是,如果你在这一点上在检查标签的宽度时间,标签宽度与显示单元格及其子视图布局后最终的宽度完全不同。

How do we get the label widths to be accurate at this point, so that they reflect their final width? Here's the code that makes it all come together:

此时我们如何使标签宽度准确,以便它们反映最终宽度?这是使这一切结合在一起的代码:

// Inside of tableView:cellForRowAtIndexPath:, after dequeueing the cell

cell.bounds = CGRect(x: 0, y: 0, width: CGRectGetWidth(tableView.bounds), height: 99999)
cell.contentView.bounds = cell.bounds
cell.layoutIfNeeded()

cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame)
cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame)
cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame)

OK, so what are we doing here? Well, you'll notice there are 3 new lines of code added. First, we need to set this table view cell's width so that it matches the actual width of the table view (this assumes the table view has already been laid out and has its final width, which should be the case). We're effectively just making the cell width correct early, since the table view is going to do this eventually.

好的,那么我们在这里做什么?好吧,您会注意到添加了 3 行新代码。首先,我们需要设置这个表格视图单元格的宽度,使其与表格视图的实际宽度相匹配(假设表格视图已经布局并具有其最终宽度,应该是这种情况)。我们实际上只是尽早使单元格宽度正确,因为表格视图最终会这样做。

You'll also notice that we're using 99999for the height. What's that about? That is a simple workaround for the problem discussed in detail here, where if your constraints require more vertical space than the current height of the cell's contentView, you get a constraint exception that doesn't actually indicate any real problem. The height of the cell or any of its subviews doesn't actually matter at this point, because we only care about getting the final widths for each label.

您还会注意到我们使用99999的是高度。那是关于什么的?这是此处详细讨论的问题的简单解决方法,如果您的约束需要比单元格 contentView 的当前高度更多的垂直空间,您会得到一个约束异常,它实际上并不表示任何真正的问题。单元格或其任何子视图的高度此时实际上并不重要,因为我们只关心获得每个标签的最终宽度。

Next, we make sure that the contentView of the cell has the same size as we just assigned to the cell itself, by setting the contentView's bounds to equal the cell's bounds. This is necessary because all of the auto layout constraints you have created are relative to the contentView, so the contentView must be the correct size in order for them to get solved correctly. Just setting the cell's size manually does notautomatically size the contentView to match.

接下来,我们通过将 contentView 的边界设置为等于单元格的边界来确保单元格的 contentView 具有与我们刚刚分配给单元格本身的大小相同的大小。这是必要的,因为您创建的所有自动布局约束都与 contentView 相关,因此 contentView 必须具有正确的大小才能正确解决它们。仅手动设置单元格的大小不会自动调整 contentView 的大小以匹配。

Finally, we force a layout pass on the cell, which will have the auto layout engine solve your constraints and update the frames of all the subviews. Since the cell & contentView now have the same widths they will at runtime in the table view, the label widths will also be correct, which means that the preferredMaxLayoutWidthset to each label will be accurate and will cause the label to wrap at the right time, which of course means the labels' heights will be set correctly when the cell is used in the table view!

最后,我们强制对单元格进行布局传递,这将使自动布局引擎解决您的约束并更新所有子视图的框架。由于 cell 和 contentView 现在具有它们在运行时在 table view 中的相同宽度,标签宽度也将是正确的,这意味着preferredMaxLayoutWidth每个标签的设置将是准确的,并将导致标签在正确的时间换行,这当然意味着在表格视图中使用单元格时标签的高度将被正确设置!

This is definitely an Apple bug in UIKit that we have to workaround for now (so please do file bug reportswith Apple so they prioritize a fix!).

这绝对是 UIKit 中的 Apple 错误,我们现在必须解决它(所以请向 Apple提交错误报告,以便他们优先修复!)。

One final note: this workaround will run into trouble if your table view cell's contentViewwidth doesn't extend the full width of the table view, for example when there is a section index showing on the right. In this case, you'll need to make sure that you manually take this into account when setting the width of the cell -- you may need to hardcode these values, something like:

最后一个注意事项:如果您的表格视图单元格的contentView宽度没有扩展表格视图的整个宽度,则此解决方法将遇到麻烦,例如当右侧显示节索引时。在这种情况下,您需要确保在设置单元格宽度时手动考虑这一点——您可能需要对这些值进行硬编码,例如:

let cellWidth = CGRectGetWidth(tableView.bounds) - kTableViewSectionIndexWidth
cell.bounds = CGRect(x: 0, y: 0, width: cellWidth, height: 99999)

回答by fogisland

I met the same issue as you and I found a simple solution to resolve it.

我遇到了和你一样的问题,我找到了一个简单的解决方案来解决它。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     // dequeue cell...

     // do autolayout staffs...or if the autolayout rule has been set in xib, do nothing

     [cell layoutIfNeeded];

     return cell;
}

And the self-sizing worked well. In my code, I laid two labels in vertical, both of them are dynamic height. The height of cell is correctly set to contain the two labels.

并且自行调整尺寸效果很好。在我的代码中,我垂直放置了两个标签,它们都是动态高度。单元格的高度正确设置为包含两个标签。

回答by user2898617

Assuming you don't have any errors with your constraints as others have suggested, this problem seems to stem from using a UILabel that allows multiple lines in conjunction with a UITableViewCellAccessory. When iOS lays out the cell and determines the height, it does not account for the offset change in width that occurs because of this accessory, and you get truncation where you wouldn't expect to.

假设您的约束没有任何错误,正如其他人所建议的那样,这个问题似乎源于使用允许多行与 UITableViewCellAccessory 结合使用的 UILabel。当 iOS 布置单元格并确定高度时,它不会考虑由于此附件而发生的宽度偏移变化,并且您会在您不期望的地方被截断。

Assuming you want the UILabel to extend the full width of the content view, I wrote up a method that fixes this for all font sizes

假设您希望 UILabel 扩展内容视图的整个宽度,我编写了一个方法来解决所有字体大小的问题

-(void)fixWidth:(UILabel *)label forCell:(UITableViewCell *)cell {
    float offset = 0;
    switch ([cell accessoryType]) {
        case UITableViewCellAccessoryCheckmark:
            offset = 39.0;
            break;
        case UITableViewCellAccessoryDetailButton:
            offset = 47.0;
            break;
        case UITableViewCellAccessoryDetailDisclosureButton:
            offset = 67.0;
            break;
        case UITableViewCellAccessoryDisclosureIndicator:
            offset = 33.0;
            break;
        case UITableViewCellAccessoryNone:
            offset = 0;
            break;
    }
    [label setPreferredMaxLayoutWidth:CGRectGetWidth([[self tableView]frame]) - offset - 8];
}

Simply put this in your cellForRowAtIndexPath

只需将其放在您的 cellForRowAtIndexPath 中

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    #Setup the cell
    ...
    // Fix layout with accessory view
    [self fixWidth:[cell label] forCell:cell];
    return cell;
}

Do this for any labels that are going to have multiple lines to adjust the width properly and then recalculate the appropriate heights. This works with dynamic font sizes as well.

对将有多行的任何标签执行此操作以正确调整宽度,然后重新计算适当的高度。这也适用于动态字体大小。

Like smileyborg had mentioned, if you weren't using the full width of the contentView you could reference the constraints and subtract them from the width as well.

就像 Smileyborg 提到的那样,如果您没有使用 contentView 的全宽,您可以引用约束并从宽度中减去它们。

Edit: I previously was running 'layoutIfNeeded' on the cell but this was creating performance issues and didn't seem to be needed anyway. Removing it hasn't caused any problems for me.

编辑:我以前在单元格上运行“layoutIfNeeded”,但这会造成性能问题,而且似乎不需要。删除它并没有给我带来任何问题。

回答by Imanou Petit

You have two problems here.

你在这里有两个问题。

1/ Right now, using Visual Format Language, your cell's vertical constraints can be translated like this:

1/ 现在,使用可视格式语言,您的单元格的垂直约束可以这样翻译:

Cell: "V:|-(10)-[nameLabel]-(67)-|"

Then, you set a second group of constraints:

然后,设置第二组约束:

Cell: "V:|-(10)-[nameLabel]-(8)-[pnrLabel]-(2)-[actionsLabel]"

Those two groups of constraints can't mix well and will reveal their ambiguity with your second problem.

这两组约束不能很好地混合,并且会暴露出它们与您的第二个问题的歧义。

2/ For some reasons, actionsLabelis limited to one line when you launch your app. Then, when you rotate your device to landscape mode, actionsLabelaccepts to be displayed with two lines or more. Then, when you rotate your device back to portrait mode, actionsLabelkeeps displaying two lines or more. But, because actionsLabelis not really part of your cell's height constraints, it overlap your cell's boundaries.

2/ 由于某些原因,actionsLabel在您启动应用程序时仅限于一行。然后,当您将设备旋转到横向模式时,actionsLabel接受以两行或更多行显示。然后,当您将设备旋转回纵向模式时,actionsLabel会继续显示两行或更多行。但是,因为actionsLabel它不是单元格高度限制的真正组成部分,所以它与单元格的边界重叠。

If you want to solve all those problems, I recommend first that you rebuild your xib file from scratch. This will cure your actionsLabelstrange behavior (two lines or more only when you rotate your device).

如果您想解决所有这些问题,我建议您首先从头开始重建您的 xib 文件。这将治愈您的actionsLabel奇怪行为(仅当您旋转设备时才出现两行或更多行)。

Then, you will have to define your constraints like this:

然后,您必须像这样定义约束:

Cell: "V:|-(10)-[nameLabel(>=21)]-(8)-[pnrLabel(>=21)]-(2)-[actionsLabel(>=21)]-(10)-|"

Of course, you can define other minimum height constraints for you labels than (>=21). In the same way, your bottom margin can be set to another value than -(10)-.

当然,您可以为标签定义其他最小高度约束,而不是(>=21). 以同样的方式,您的下边距可以设置为其他值而不是-(10)-

Addendum

附录

In order to answer your question, I created a simple project with the previous constraints pattern in my .xib file. The following image may help you build your own constraints.

为了回答您的问题,我在我的 .xib 文件中使用先前的约束模式创建了一个简单的项目。下图可能会帮助您构建自己的约束。

enter image description here

在此处输入图片说明

回答by Hardy_Germany

I tried the very easy and elegant looking solution of "fogisland" - and it did not work. Luckily I found out that one additional line makes it work in alle directions. Just tell the system that you not only suggest a new layout (layoutIfNeeded), you explicitly ask for it (setNeedLayout)

我尝试了“fogisland”的非常简单和优雅的解决方案 - 但它没有用。幸运的是,我发现额外的一行使其在所有方向上都能正常工作。只需告诉系统您不仅建议新布局 (layoutIfNeeded),而且您明确要求它 (setNeedLayout)

    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    return cell