ios AutoLayout 多行 UILabel 切断了一些文本

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23398042/
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 00:03:27  来源:igfitidea点击:

AutoLayout multiline UILabel cutting off some text

iosobjective-cuilabelautolayoutuistoryboard

提问by Simon McLoughlin

I'm trying to learn auto layout, finally thought I was getting the hang on it when this happened. I'm playing about with prototype cells and labels. I'm trying to have a

我正在尝试学习自动布局,最后以为发生这种情况时我已经掌握了它。我正在研究原型单元格和标签。我试图拥有一个

  • Title, titleLbl- blue box in picture
  • Money, moneyLbl- green box in picture
  • Subtitle / Description, subTitleLbl- red box in picture
  • 标题, titleLbl- 图片中的蓝色框
  • 钱,moneyLbl-图中绿色框
  • 副标题/说明,subTitleLbl-图片中的红框

So far I have this:

到目前为止,我有这个:

iphone screenshot

iphone截图

What I want to achieve is:

我想要实现的是:

  • The green box should always display on 1 line fully
  • The value inside the green box should be centred based on the blue box
  • The blue box will take the remaining space (-8px for horizontal constraint between the 2) and display on multiple lines if necessary
  • The red box should be underneath and display as many lines as needed.
  • 绿色框应始终完全显示在 1 行上
  • 绿色框内的值应以蓝色框为中心
  • 蓝色框将占用剩余空间(-8px 用于 2 之间的水平约束)并在必要时显示在多行上
  • 红色框应在下方并根据需要显示尽可能多的行。

As you can see this is nearly all working, with the exception of the example in the last row. I've tried to research this and from what I can gather it is something to do with the intrinsic content size. Many posts online recommend setting setPreferredMaxLayoutWidth, i've done this in multiple places for the titleLbland it has had no effect. Only when hardcoding it to 180did I get the results, but it also added whitespace / padding to the other blue boxes on top / underneath the text, greatly increasing the space between the red / blue boxes.

正如您所看到的,除了最后一行中的示例外,这几乎所有内容都可以正常工作。我试图对此进行研究,据我所知,这与内在内容大小有关。网上很多帖子都推荐设置setPreferredMaxLayoutWidth,我已经在多个地方这样做了titleLbl,但没有效果。只有在对其进行硬编码时,180我才得到结果,但它还向文本顶部/下方的其他蓝色框添加了空白/填充,大大增加了红色/蓝色框之间的空间。

Here are my constraints:

这是我的限制条件:

Title: title constraints

标题: 标题限制

Money: money constraints

钱: 金钱限制

Subtitle: subtitle constraints

字幕: 字幕限制

Based on tests i've run with other text samples it does appear to be using the width as shown in the storyboard, but any attempt to correct it isn't working.

根据我使用其他文本示例运行的测试,它似乎确实使用了故事板中显示的宽度,但任何纠正它的尝试都不起作用。

I have created an ivar of the cell and used it to create the height of the cell:

我创建了单元格的 ivar 并用它来创建单元格的高度:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    [sizingCellTitleSubMoney.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
    [sizingCellTitleSubMoney.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
    [sizingCellTitleSubMoney.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];

    [sizingCellTitleSubMoney layoutIfNeeded];

    CGSize size = [sizingCellTitleSubMoney.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    return size.height+1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MATitleSubtitleMoneyTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifierTitleSubTitleMoney];

    if(!cell)
    {
        cell = [[MATitleSubtitleMoneyTableViewCell alloc] init];
    }


    cell.titleLbl.layer.borderColor = [UIColor blueColor].CGColor;
    cell.titleLbl.layer.borderWidth = 1;

    cell.subtitleLbl.layer.borderColor = [UIColor redColor].CGColor;
    cell.subtitleLbl.layer.borderWidth = 1;

    cell.moneyLbl.layer.borderColor = [UIColor greenColor].CGColor;
    cell.moneyLbl.layer.borderWidth = 1;


    [cell.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
    [cell.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
    [cell.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];

    return cell;
}

I've tried setting setPreferredMaxLayoutWidthinside the cell's layout subviews:

我试过setPreferredMaxLayoutWidth在单元格的布局子视图中设置:

- (void)layoutSubviews
{
    [super layoutSubviews];

    NSLog(@"Money lbl: %@", NSStringFromCGRect(self.moneyLbl.frame));
    NSLog(@"prefferredMaxSize: %f \n\n", self.moneyLbl.frame.origin.x-8);

    [self.titleLbl setPreferredMaxLayoutWidth: self.moneyLbl.frame.origin.x-8];
}

log:

日志:

2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] Money lbl: {{209, 20}, {91, 61}}
2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] prefferredMaxSize: 201.000000 

Which is what I would expect as there is 8px between the 2 elements and the console verifies this. It works perfectly on the first row no matter how much text I add to the blue box, which is the same as the storyboard, but any increase to the green box throws off the width calculation. I've tried setting this in the tableView:willDisplayCelland the tableView:heightForRowAtIndexPath:as well based on answers from other questions. But no matter what it just doesn't layout.

这是我所期望的,因为 2 个元素之间有 8px 并且控制台验证了这一点。无论我向蓝色框添加多少文本,它都可以在第一行完美运行,这与情节提要相同,但是对绿色框的任何增加都会忽略宽度计算。我已经尝试根据其他问题的答案在 thetableView:willDisplayCell和 the 中设置它tableView:heightForRowAtIndexPath:。但无论如何它都没有布局。

Any help, ideas or comments would be greatly appreciated

任何帮助、想法或评论将不胜感激

采纳答案by Simon McLoughlin

Found an even better answer after reading this: http://johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights

阅读本文后找到了更好的答案:http: //johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights

creating a UILabelsubclass to override layoutSubviewslike so:

创建一个UILabel子类来覆盖,layoutSubviews如下所示:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);

    [super layoutSubviews];
}

This ensures the preferredMaxLayoutWidthis always correct.

这确保了preferredMaxLayoutWidth总是正确的。

Update:

更新:

After several more release's of iOS and testing more usecase's i've found the above to not be sufficient for every case. As of iOS 8 (I believe), under some circumstances only didSetboundswas called in time before the screen load.

在发布了多个 iOS 版本并测试了更多用例之后,我发现上述内容对于每种情况都不够。从 iOS 8 开始(我相信),在某些情况下仅didSetbounds在屏幕加载之前及时调用。

In iOS 9 very recently I came across another issue when using a modal UIVIewControllerwith a UITableView, that both layoutSubviewsand set boundswere being called afterheightForRowAtIndexPath. After much debugging the only solution was to override set frame.

在iOS中9最近我遇到另一个问题,当使用一个模式来UIVIewControllerUITableView,既layoutSubviewsset bounds被调用heightForRowAtIndexPath。经过多次调试,唯一的解决方案是覆盖set frame.

The below code now seems to be necessary to ensure it works across all iOS's, controllers, screen sizes etc.

现在似乎需要下面的代码来确保它适用于所有 iOS、控制器、屏幕尺寸等。

override public func layoutSubviews()
{
    super.layoutSubviews()
    self.preferredMaxLayoutWidth = self.bounds.width
    super.layoutSubviews()
}

override public var bounds: CGRect
{
    didSet
    {
        self.preferredMaxLayoutWidth = self.bounds.width
    }
}

override public var frame: CGRect
{
    didSet
    {
        self.preferredMaxLayoutWidth = self.frame.width
    }
}

回答by Nikita Took

I'm not sure I understand you right and my solution is really far from the best one, but here it is:

我不确定我是否理解正确,而且我的解决方案与最好的解决方案相去甚远,但它是:

I've added a height constraint on label that was cut, connected that constraint to cell's outlet. I've overrided layoutSubview method:

我在被切割的标签上添加了一个高度约束,将该约束连接到单元格的出口。我已经覆盖了 layoutSubview 方法:

- (void) layoutSubviews {
    [super layoutSubviews];

    [self.badLabel setNeedsLayout];
    [self.badLabel layoutIfNeeded];

    self.badLabelHeightConstraint.constant = self.badLabel.intrinsicContentSize.height;
}

and viola! Please try that method and tell me results, thank you.

和中提琴!请尝试该方法并告诉我结果,谢谢。

回答by Mike Demidov

Omg, just have had the same issue. The @Simon McLoughlin's answer did not help me. But

Omg,刚刚遇到了同样的问题。@Simon McLoughlin 的回答对我没有帮助。但

titleLabel.adjustsFontSizeToFitWidth = true

did a magic! It works, I don't know why but it does. And yes, your label must have an explicit preferredMaxLayoutWidth value either.

变魔术了!它有效,我不知道为什么,但确实有效。是的,您的标签也必须具有明确的 preferredMaxLayoutWidth 值。

回答by Aleksei Minaev

I did spend some time with wrapping up the cell, looking like this:

我确实花了一些时间来包装单元格,看起来像这样:

+--------------------------------------------------+
| This is my cell.contentView                      |
| +------+ +-----------------+ +--------+ +------+ |
| |      | |    multiline    | | title2 | |      | |
| | img  | +-----------------+ +--------+ | img  | |
| |      | +-----------------+ +--------+ |      | |
| |      | |     title3      | | title4 | |      | |
| +------+ +-----------------+ +--------+ +------+ |
|                                                  |
+--------------------------------------------------+

After many attempts I did came with a solution how to use prototype cell that is actually working.

经过多次尝试,我确实提出了如何使用实际工作的原型单元的解决方案。

Main trouble was that my labels could be or could be not there. Every element should be optional.

主要的问题是我的标签可能存在也可能不存在。每个元素都应该是可选的。

First of all, our 'prototype' cell should only layout stuff when we are not in "real" environment.

首先,当我们不在“真实”环境中时,我们的“原型”单元应该只布局东西。

Therefore, I did create flag 'heightCalculationInProgress'.

因此,我确实创建了标志“heightCalculationInProgress”。

When somebody(tableView's dataSource) is asking to calculate height we are providing data to prototype cell using block: And then we're asking our prototype cell to layout the content and grabbing height out of it. Simple.

当有人(tableView 的数据源)要求计算高度时,我们使用 block 向原型单元格提供数据:然后我们要求原型单元格布局内容并从中获取高度。简单的。

In order to avoid iOS7 bug with layoutSubview recursion there is a "layoutingLock" variable

为了避免带有 layoutSubview 递归的 iOS7 错误,有一个“layoutingLock”变量

- (CGFloat)heightWithSetupBlock:(nonnull void (^)(__kindof UITableViewCell *_Nonnull cell))block {
    block(self);

    self.heightCalculationInProgress = YES;

    [self setNeedsLayout];
    [self layoutIfNeeded];

    self.layoutingLock = NO;
    self.heightCalculationInProgress = NO;

    CGFloat calculatedHeight = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    CGFloat height = roundf(MAX(calculatedHeight, [[self class] defaultHeight]));

    return height;
}

This 'block' from the outside could looks like this:

这个来自外部的“块”可能如下所示:

[prototypeCell heightWithSetupBlock:^(__kindof UITableViewCell * _Nonnull cell) {
    @strongify(self);
    cell.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 0);
    cell.dataObject = dataObject;
    cell.multilineLabel.text = @"Long text";
    // Include any data here
}];

And now starts the most interesting part. Layouting:

现在开始最有趣的部分。布局:

- (void)layoutSubviews {
    if(self.layoutingLock){
        return;
    }

    // We don't want to do this layouting things in 'real' cells
    if (self.heightCalculationInProgress) {

    // Because of:
    // Support for constraint-based layout (auto layout)
    // If nonzero, this is used when determining -intrinsicContentSize for multiline labels

    // We're actually resetting internal constraints here. And then we could reuse this cell with new data to layout it correctly
        _multilineLabel.preferredMaxLayoutWidth = 0.f;
        _title2Label.preferredMaxLayoutWidth = 0.f;
        _title3Label.preferredMaxLayoutWidth = 0.f;
        _title4Label.preferredMaxLayoutWidth = 0.f;
    }

    [super layoutSubviews];

    // We don't want to do this layouting things in 'real' cells
    if (self.heightCalculationInProgress) {

        // Make sure the contentView does a layout pass here so that its subviews have their frames set, which we
        // need to use to set the preferredMaxLayoutWidth below.
        [self.contentView setNeedsLayout];
        [self.contentView layoutIfNeeded];

        // Set the preferredMaxLayoutWidth of the mutli-line bodyLabel based on the evaluated width of the label's frame,
        // as this will allow the text to wrap correctly, and as a result allow the label to take on the correct height.
        _multilineLabel.preferredMaxLayoutWidth = CGRectGetWidth(_multilineLabel.frame);
        _title2Label.preferredMaxLayoutWidth = CGRectGetWidth(_title2Label.frame);
        _title3Label.preferredMaxLayoutWidth = CGRectGetWidth(title3Label.frame);
        _title4Label.preferredMaxLayoutWidth = CGRectGetWidth(_title4Label.frame);
    }

    if (self.heightCalculationInProgress) {
        self.layoutingLock = YES;
    }
}