UILabel 有时不能正确换行(自动布局)

Posted

技术标签:

【中文标题】UILabel 有时不能正确换行(自动布局)【英文标题】:UILabel not wrapping text correctly sometimes (auto layout) 【发布时间】:2013-03-20 01:41:09 【问题描述】:

我在视图中有一个 uilabel 设置。它没有宽度约束,但其宽度由缩略图图像的前导约束和视图边缘的尾随约束确定。

标签设置为 0 行,并自动换行。我的理解是,这应该会导致 uilabel 的框架增长,而且有时确实会增长。 (在自动布局之前,我会在代码中计算和更新标签的框架)。

所以结果是,它在某些情况下可以正常工作,而在其他情况下不能正常工作。看到大多数单元格在那里正常工作,但最后一个单元格似乎太大了。事实上,它的大小是合适的。标题“Fair Oaks Smog Check Test”实际上以“Only”结尾。所以我对单元格大小的计算是正确的,应该是那个大小。但是,无论出于何种原因,标签都不会包装文本。它的框架宽度没有向右延伸,所以这不是问题。

那么这里发生了什么?它是 100% 一致的,总是在那个单元格上而不是在它上面的那个单元格上,这让我认为它与文本的大小有关,一旦这个视图被添加到单元格中,UILabel 就不会重新布局文本(其中使其实际上宽度更小)。

有什么想法吗?

一些附加信息

单元格的高度是根据我创建并存储在静态变量中的一个示例单元格计算得出的:

- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 
    if (self.items.count == 0) 
        return 60;
    
    static TCAnswerBuilderCell *cell = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred,
                  ^
                      // get a sample cellonce
                      cell = [tableView dequeueReusableCellWithIdentifier:TC_ANSWER_BUILDER_CELL];
                  );
    [cell configureCellWithThirdPartyObject:self.items[indexPath.row]];
    return [cell heightForCellWithTableWidth:self.tableView.frame.size.width];

我使用我的数据对象动态配置单元格,然后调用我拥有的一个方法来计算具有给定表格宽度的单元格的高度(不能总是依赖最初正确的单元格框架) .

这反过来又在我的视图中调用了一个 height 方法,因为它确实是标签所在的位置:

- (CGFloat)heightForCellWithTableWidth:(CGFloat)tableWidth 
    // subtract 38 from the constraint above
    return [self.thirdPartyAnswerView heightForViewWithViewWidth:tableWidth - 38];

此方法通过计算出标签的正确宽度来确定高度,然后进行计算:

- (CGFloat)heightForViewWithViewWidth:(CGFloat)viewWidth 
    CGFloat widthForCalc = viewWidth - self.imageFrameLeadingSpaceConstraint.constant - self.thumbnailFrameWidthConstraint.constant - self.titleLabelLeadingSpaceConstraint.constant;
    CGSize size = [self.titleLabel.text sizeWithFont:self.titleLabel.font constrainedToSize:CGSizeMake(widthForCalc, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
    CGFloat returnHeight = self.frame.size.height - self.titleLabel.frame.size.height + size.height;
    CGFloat height = returnHeight < self.frame.size.height ? self.frame.size.height : returnHeight;
    return height;

这 100% 正确。

单元格显然是在 cellForRowAtIndexPath 中创建并立即配置的:

if (self.items.count > 0) 
    TCAnswerBuilderCell *cell = [tableView dequeueReusableCellWithIdentifier:TC_ANSWER_BUILDER_CELL forIndexPath:indexPath];
    [cell configureCellWithThirdPartyObject:self.items[indexPath.row]];
    return cell;

在单元格的配置中,我的视图是从笔尖加载的(它在其他地方重复使用,这就是它不直接在单元格中的原因)。单元格添加如下:

- (void) configureCellWithThirdPartyObject:(TCThirdPartyObject *)object 
    self.detailDisclosureImageView.hidden = NO;
    if (!self.thirdPartyAnswerView) 
        self.thirdPartyAnswerView = [TCThirdPartyAPIHelper thirdPartyAnswerViewForThirdPartyAPIServiceType:object.thirdPartyAPIType];
        self.thirdPartyAnswerView.translatesAutoresizingMaskIntoConstraints = NO;
        [self.contentView addSubview:self.thirdPartyAnswerView];
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[_thirdPartyAnswerView]-38-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:NSDictionaryOfVariableBindings(_thirdPartyAnswerView)]];
    
    [self.thirdPartyAnswerView configureViewForThirdPartyObject:object forViewStyle:TCThirdPartyAnswerViewStyleSearchCell];

最后我的视图配置是这样的:

- (void) configureViewForThirdPartyObject:(TCTPOPlace *)object forViewStyle:(TCThirdPartyAnswerViewStyle) style 
    self.titleLabel.text = object.name;
    self.addressLabel.text = [NSString stringWithFormat:@"%@, %@, %@", object.address, object.city, object.state];
    self.ratingsLabel.text = [NSString stringWithFormat:@"%d Ratings", object.reviewCount];
    NSString *ratingImageName = [NSString stringWithFormat:@"yelp_star_rating_%.1f.png", object.rating];
    UIImage *ratingsImage = [UIImage imageNamed:ratingImageName];
    if (ratingsImage) 
        self.ratingImageView.image = ratingsImage;
    
    if (object.imageUrl) 
        [self.thumbnailImageView setImageWithURL:[NSURL URLWithString:object.imageUrl] completed:nil];
    

一种解决方案,但我不明白为什么

    我的子视图设计为 320 宽度,但没有自己的宽度限制 子视图已添加到单元格中,但具有如下所示的水平约束: @"|[_thirdPartyAnswerView]-38-|" 视图是在添加到单元格后立即配置的,这意味着 titleLabel 的文本是在那时设置的。 无论出于何种原因,文本的布局都好像视图具有完整的 320 而不是 282。 标签从未更新,即使子视图的框架已更新为 282,并且标签上有一些约束可以使其保持正确的大小。 将 xib 中的视图大小更改为 282 可解决此问题,因为标签的开头大小正确。

我仍然不明白为什么在父视图的大小更新后标签没有重新布局,因为它同时具有前导和尾随约束。

已解决

请看下面马特的回答:https://***.com/a/15514707/287403

如果您没有阅读评论,主要问题是我在设计宽度大于显示宽度的视图时(在某些情况下)通过 IB 在不知不觉中设置了preferredMaxLayoutWidthpreferredMaxLayoutWidth 用于确定文本换行的位置。因此,即使我的视图和 titleLabel 正确调整了大小,preferredMaxLayoutWidth 仍处于旧值,并导致在意外点处换行。将 titleLabel 设置为自动大小(IB 中的⌘=),并在调用 super 之前在 layoutSubviews 中动态更新 preferredMaxLayoutWidth 是关键。谢谢马特!

【问题讨论】:

您是否尝试在渲染期间捕获此单元格以查看所有属性?作为后续,在初始屏幕上呈现的单元格是否会发生这种情况?对于具有不同高度单元格的列表视图,我遇到了类似的问题。启用自动调整大小后,单元格有时会错误地呈现高度。 与细胞重复使用有任何联系吗?例如单元格滚动出站点并返回 - 效果与以前一样吗?单元格是在该 tableview 最初可见的过去绘制的,效果还是一样吗? ... 无连接可重复使用。如果立即绘制或稍后滚动,结果相同。 你救了我的命,伙计! preferredMaxLayoutWidth 正在杀死它 【参考方案1】:

我编写了一个应用程序,该应用程序在表格中的单元格中使用五个标签的自动布局,其单元格具有不同的高度,其中标签根据其中的内容自行调整大小,并且确实有效。因此,我将建议您遇到问题的原因可能是您的约束未充分确定布局 - 也就是说,您的单元格元素布局不明确。我无法检验该假设,因为我看不到您的限制。但是您可以在调试器中暂停时使用po [[UIWindow keyWindow] _autolayoutTrace] 轻松检查(我认为)。

另外我还有一个建议(很抱歉只是向你扔东西):确保你设置了标签的preferredMaxLayoutWidth。这很关键,因为它是标签停止水平增长并开始垂直增长的宽度。

【讨论】:

酷。谢谢马特!我将尝试自动布局跟踪。那么您是否建议将标签保留为自然大小(IB 中的⌘=),然后设置preferredMaxLayoutWidth?另外,我需要在什么时候设置它?当我在将视图添加到单元格之后配置视图时,或者像 layoutSubviews 之类的其他点? 顺便说一句,你说得对,我在子视图及其内容上有 AMBIGUOUS LAYOUT。 screencast.com/t/xOPyF3JleCm 现在,如何确定它的模棱两可之处。 你很好!你很不错! ;) 我在 layoutSubviews 的标签上设置了 preferredMaxLayoutWidth(在调用 super 之前)。不确定这是否是一个好地方,但我标签的最大宽度取决于父视图的大小,所以我需要在设置它之前对其进行评估。如果它是动态的,那是设置它的正确位置吗? 仅供任何遇到此问题的人参考,设置preferredMaxLayoutWidth 是真正的解决方案。否则,通过在 IB 中设置宽度并使用约束,在更新父视图大小之前评估标签的最大宽度和后续包装。然后文本就丢失了(这很奇怪)。我最终在layoutSubviews 中设置了preferredMaxLayoutWidth(在调用super 之前),因为那时父框架是已知的,而我的preferredMaxLayoutWidth 是动态的。我确实有模棱两可的布局,但这不是问题的原因。 还有一点需要注意。可能不是过早评估导致我的问题的标签宽度,但更多的事实是,如果您设置的宽度不是自动大小(IB 中的⌘=),您实际上是在不知情的情况下设置preferredMaxLayoutWidth。由于我在 IB 中的视图更大,所以我的 preferredMaxLayoutWidth 也更大,这就是用于评估包装发生位置的方法。事实上,我认为这几乎肯定是正在发生的事情。所以使用自动大小(IB 中的⌘=)和preferredMaxLayoutWidth【参考方案2】:

我遇到了同样的问题,并使用this answer 的建议解决了它。在 UILabel 的子类中,我放置了以下代码:

- (void)layoutSubviews

    [super layoutSubviews];
    self.preferredMaxLayoutWidth = self.bounds.size.width;

我不明白为什么这不是 UILabel 的默认行为,或者至少为什么不能通过标志启用此行为。

我有点担心在布局过程中设置了preferredMaxLayoutWidth,但我认为没有简单的方法。

【讨论】:

ios 8 和 9 中它默认行为,您可以通过标志启用它,并且可以删除该代码。跨度> @matt 你指的是什么标志? @RyanM:在 IB 中,您可以选择“自动”作为首选布局宽度。【参考方案3】:

另外,请检查您是否将整数传递给代码中的布局约束。

对我来说,经过一些计算(例如 convertPoint:toView:),我传入了类似 23.99999997 的内容,最终这导致一个 2 行标签显示为单行标签(尽管它的框架似乎是计算正确)。就我而言,CGRectIntegral 成功了!

舍入错误可能会杀死你:)

【讨论】:

以上是关于UILabel 有时不能正确换行(自动布局)的主要内容,如果未能解决你的问题,请参考以下文章

自动布局和 UILabel 的问题

多行 UILabel 没有正确换行 [重复]

为自动布局设置数字 UILabel 以正确计算内在内容大小

UILabel 自动换行 和支持换行符

自动布局:UILabel 无法正确调整长文本的大小

如何在 iOS 的 UILabel/UITextView 中处理多种屏幕尺寸的中文自动换行?