AutoLayout多行UILabel切断了一些文字

时间:2014-04-30 21:08:21

标签: ios objective-c uilabel autolayout uistoryboard

我正在尝试学习自动布局,最后我以为在发生这种情况时我会抓住它。我正在玩原型细胞和标签。我想要一个

  • 标题,titleLbl - 图片中的蓝框
  • Money,moneyLbl - 图片中的绿框
  • 字幕/描述,subTitleLbl - 图片中的红框

到目前为止,我有这个:

iphone screenshot

我想要实现的目标是:

  • 绿色框应始终显示在1行
  • 绿框内的值应基于蓝框
  • 居中
  • 蓝色框将占用剩余空间(-8px用于2之间的水平约束)并在必要时显示在多行上
  • 红色框应位于下方,并根据需要显示多行。

正如您所看到的,除了最后一行中的示例外,这几乎全部正常工作。我试图对此进行研究,从我可以收集的内容来看,它与内在的内容大小有关。很多在线帖子都建议设置setPreferredMaxLayoutWidth,我已经在titleLbl的多个位置完成了此操作,但它没有任何效果。只有在将其硬编码到180时才能获得结果,但它还在文本顶部/下方的其他蓝框中添加了空格/填充,大大增加了红/蓝框之间的空间。

以下是我的约束:

名称: title constraints

金钱: money constraints

字幕: subtitle constraints

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

我创建了一个单元格的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;
}

我已尝试在单元格的布局子视图中设置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];
}

日志:

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 

这是我期望的,因为2个元素之间有8px,控制台会验证这一点。无论我在蓝色框中添加多少文本(与故事板相同),它在第一行上都能完美运行,但绿色框的任何增加都会导致宽度计算失败。我已尝试根据其他问题的答案在tableView:willDisplayCelltableView:heightForRowAtIndexPath:中设置此项。但无论它不是什么布局都不会。

非常感谢任何帮助,想法或评论

4 个答案:

答案 0 :(得分:27)

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

创建UILabel子类以覆盖layoutSubviews,如下所示:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);

    [super layoutSubviews];
}

这可确保preferredMaxLayoutWidth始终正确。

<强>更新

经过几次iOS发布并测试更多用例后,我发现上述内容并不适用于所有情况。从iOS 8开始(我相信),在某些情况下,只有didSet bounds在屏幕加载之前及时调用。

在最近的iOS 9中,当使用带有UIVIewController的模态UITableView时,我遇到了另一个问题,layoutSubviewsset bounds被称为之后 heightForRowAtIndexPath。经过大量调试后,唯一的解决方案是覆盖set frame

以下代码现在似乎是必要的,以确保它适用于所有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
    }
}

答案 1 :(得分:2)

我不确定我是否理解你,我的解决方案与最好的解决方案相差甚远,但现在是:

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

- (void) layoutSubviews {
    [super layoutSubviews];

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

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

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

答案 2 :(得分:1)

我确实花了一些时间来整理细胞,看起来像这样:

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

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

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

首先,我们的'原型'单元格应该只在我们不处于“真实”环境时布局。

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

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

为了避免iOS7与layoutSubview递归的错误,有一个“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;
}

来自外部的“阻止”可能如下所示:

[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
}];

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

- (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;
    }
}

答案 3 :(得分:0)

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

titleLabel.adjustsFontSizeToFitWidth = true

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