【问题标题】:AutoLayout multiline UILabel cutting off some textAutoLayout 多行 UILabel 截断一些文本
【发布时间】:2014-06-17 08:40:22
【问题描述】:

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

  • 标题,titleLbl - 图片中的蓝色框
  • 钱,moneyLbl - 图片中的绿框
  • 字幕/说明,subTitleLbl - 图片中的红框

到目前为止,我有这个:

我想要实现的是:

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

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

这是我的限制:

标题:

金钱:

字幕:

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

我创建了一个单元格的 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: 中进行设置。但无论如何它只是没有布局。

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

【问题讨论】:

    标签: ios objective-c uilabel autolayout uistoryboard


    【解决方案1】:

    我不确定我的理解是否正确,我的解决方案远非最佳解决方案,但它是:

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

    - (void) layoutSubviews {
        [super layoutSubviews];
    
        [self.badLabel setNeedsLayout];
        [self.badLabel layoutIfNeeded];
    
        self.badLabelHeightConstraint.constant = self.badLabel.intrinsicContentSize.height;
    }
    

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

    【讨论】:

    • 感谢您的意见,但我很抱歉,这太离谱了。问题(如我的回答所述)是在绿色标签被拉伸以适应其内容之前,它使用了绿色标签中的 X 位置。在大多数情况下使用多行标签时,您需要设置setPreferredMaxLayoutWidth,我需要计算绿色的显示宽度以便计算蓝色的这个值。一旦设置好,它就会计算出正确的高度
    【解决方案2】:

    阅读此文后找到更好的答案: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 中,我在使用模式 UIVIewControllerUITableView 时遇到了另一个问题,layoutSubviewsset bounds 都被称为 after 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
        }
    }
    

    【讨论】:

      【解决方案3】:

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

      titleLabel.adjustsFontSizeToFitWidth = true
      

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

      【讨论】:

      • 这并不是一个理想的解决方案。而不是让标签增长,这只是让字体增加/减少以适应固定宽度。这将导致您的 UI 看起来非常不一致。我在 iOS 和需要额外代码的用例中发现了更多错误。我更新了上面的答案。
      【解决方案4】:

      我确实花了一些时间把牢房包起来,看起来像这样:

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

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

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

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

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

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

      为了避免 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;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-03
        • 2012-07-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-04
        相关资源
        最近更新 更多