iOS - 带约束的多行 UILabel 高度

Posted

技术标签:

【中文标题】iOS - 带约束的多行 UILabel 高度【英文标题】:iOS - Multiline UILabel Height With Constraints 【发布时间】:2014-08-02 22:30:01 【问题描述】:

我正在尝试创建一个可重用的消息UIView 子类,该子类根据其UILabel 中的文本调整其高度。这个答案说它不能完成。真的是这样吗? ios message cell width/height using auto layout

我的问题是 UILabel 的 CGFrame 的高度太大。 (UILabel 的背景颜色为绿色。)

这是我的代码(顺便说一下,[autolayoutView]translatesAutoresizingMaskIntoConstraints 设置为 NO):

SSLStickyView *stickyView = [[SSLStickyView alloc] initWithText:@"Try keeping a steady beat to help me get past the notes! Press the bass drum to jump!"];

SSLStickyView.m

- (instancetype)initWithText:(NSString *)text

    self = [super initWithFrame:CGRectZero];
    if (self)
    

        _stickyImageView = [UIImageView autoLayoutView];
        _stickyImageView.backgroundColor = [UIColor blueColor];
        _stickyImageView.image = [UIImage imageNamed:@"element_sticky"];
        [self addSubview:_stickyImageView];

        float padding = 5;
        NSMutableAttributedString *attributedText =
        [[NSMutableAttributedString alloc]
         initWithString:text
         attributes:@
         
         NSFontAttributeName: [UIFont boldSystemFontOfSize:30],
         NSForegroundColorAttributeName: [UIColor purpleColor]
         ];

        UILabel *textLabel = [UILabel autoLayoutView];
        textLabel.preferredMaxLayoutWidth = 50;
        textLabel.attributedText = attributedText;
        textLabel.numberOfLines = 0; // unlimited number of lines
        textLabel.lineBreakMode = NSLineBreakByWordWrapping;
        textLabel.backgroundColor = [UIColor greenColor];

        [_stickyImageView addSubview:textLabel];

        NSLayoutConstraint *stickyWidthPin =
        [NSLayoutConstraint constraintWithItem:_stickyImageView
                                     attribute:NSLayoutAttributeWidth
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:textLabel
                                     attribute:NSLayoutAttributeWidth
                                    multiplier:1
                                      constant:padding * 2];
        NSLayoutConstraint *stickyHeightPin =
        [NSLayoutConstraint constraintWithItem:_stickyImageView
                                     attribute:NSLayoutAttributeHeight
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:textLabel
                                     attribute:NSLayoutAttributeHeight
                                    multiplier:1
                                      constant:0];
        NSLayoutConstraint *stickyTextLabelTop =
        [NSLayoutConstraint constraintWithItem:_stickyImageView
                                     attribute:NSLayoutAttributeTop
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:textLabel
                                     attribute:NSLayoutAttributeTop
                                    multiplier:1
                                      constant:0];
        NSLayoutConstraint *stickyTextLeftPin = [NSLayoutConstraint constraintWithItem:_stickyImageView
                                                                            attribute:NSLayoutAttributeLeft
                                                                            relatedBy:NSLayoutRelationEqual
                                                                               toItem:textLabel
                                                                            attribute:NSLayoutAttributeLeft
                                                                           multiplier:1
                                                                             constant:-padding * 2];
        NSDictionary *views = NSDictionaryOfVariableBindings(_stickyImageView, textLabel);
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[_stickyImageView]" options:0 metrics:nil views:views]];
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_stickyImageView]" options:0 metrics:nil views:views]];
        [self addConstraints:@[stickyWidthPin, stickyHeightPin, stickyTextLeftPin, stickyTextLabelTop]];
        self.backgroundColor = [UIColor whiteColor];

    

    return self;

【问题讨论】:

【参考方案1】:

让您的 superview 了解其内容大小的变化,并相应地调整 superview 的宽度和高度。由于如何做到这一点可能不是很明显,我提供了一个 UIView 子类,它将根据其内容(一个 UILabel)调整自身大小。

注意:仅向 ReusableMessageView 添加会影响其在其父视图中的位置的约束。 ReusableMessageView 将根据消息调整其宽度/高度。

@interface ReusableMessageView : UIView
-(instancetype)initWithMessage:(NSString *)message preferredWidth:(CGFloat)width;
-(void)setMessage:(NSString *)message;
@end

@implementation ReusableMessageView 
    UILabel *_label;


-(instancetype)initWithMessage:(NSString *)message preferredWidth:(CGFloat)width 
    if (self = [super init]) 
        self.translatesAutoresizingMaskIntoConstraints = NO;
        //setup label
        _label = [UILabel new];
        _label.translatesAutoresizingMaskIntoConstraints = NO;
        _label.text = message;
        _label.preferredMaxLayoutWidth = width;
        _label.numberOfLines = 0;
        [self addSubview:_label];
    
    return self;


-(void)layoutSubviews 
    [super layoutSubviews];
    // remove all previously added constraints
    [self removeConstraints:self.constraints];

    CGFloat width = _label.bounds.size.width;
    CGFloat height = _label.bounds.size.height;

    NSLayoutConstraint *c1,*c2,*c3,*c4;
    // set the view's width/height to be equal to the label's width/height
    c1 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:width];
    c2 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:height];
    // center the label
    c3 = [NSLayoutConstraint constraintWithItem:_label attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
    c4 = [NSLayoutConstraint constraintWithItem:_label attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0];
    // add all constraints
    [self addConstraints:@[c1,c2,c3,c4]];


-(void)setMessage:(NSString *)message 
    _label.text = message;
    // once the message changes, the constraints need to be adjusted
    [self setNeedsLayout];

@end

这可以通过重用现有约束并仅更改每个约束的“常量”属性来改进。如果您这样做,您甚至可以为更改设置动画。

这是一个在 viewController 的 viewDidLoad 方法中使用的示例:

ReusableMessageView *view = [[ReusableMessageView alloc]initWithMessage:@"This is the first message that is rather long in order to exaggerate the change in size" preferredWidth:50.0];
[self.view addSubview:view];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
// demonstrate the change in size
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
    [view setMessage:@"This is the second message"];
);

【讨论】:

将代码复制到新项目(在viewDidAppear 中调用)时出现以下错误:*** Assertion failure in -[RLReusableView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794Auto Layout still required after executing -layoutSubviews. RLReusableView's implementation of -layoutSubviews needs to call super. 有趣。你确定你在 RLReusableView 的 layoutSubviews 方法中实现了 [super layoutSubviews]?您是否覆盖任何其他方法? 是的,这很有趣。我确定我实现了 [super layoutSubviews]。我也发现了这个问题,但我找不到不特定于 UITableViewCell 的答案。有趣的是,我可以将 layoutSubviews 中的所有约束代码移动到 init 中,并且它可以正常工作。奇怪,不是吗?【参考方案2】:

当然可以,但不能没有代码。视图不会根据其内部内容的约束和大小自动变小。这不是自动布局的行为方式。

可以根据其中的约束和大小(使用systemLayoutFittingSize)确定某物需要的大小,并且您可以设置该东西代码中的那个大小。但这不会通过某种魔法发生。

【讨论】:

你的意思是这样的吗? textLabel.frame = CGRectMake(textLabel.frame.origin.x, textLabel.frame.origin.y, [textLabel systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width, [textLabel systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height); 什么时候调用?而且我觉得我上面的代码是错误的,因为它设置了框架,并且在使用自动布局时设置框架通常是禁止的。 textLabel 将自行调整大小;无需设置其大小。我说的是你问的关于如何调整 包含 textLabel 的 UIView 以响应标签大小变化的问题。我的意思是,如果这是您想要做的,您需要以某种方式手动执行此操作。是的,如果您使用自动布局,则必须使用约束来完成,而不是直接设置框架。

以上是关于iOS - 带约束的多行 UILabel 高度的主要内容,如果未能解决你的问题,请参考以下文章

多行 UILabel 高度,横向自动布局更高

iOS 约束:如何给 UILabel 高度约束优先级

我怎样才能得到 UILabel 的高度?

boundingRectWithSize 为多行标签返回错误的高度值

如何在 uilabel 中使用多行并根据 Swift 中的标签高度自动扩展 uitableview 行高?

iOS:多行 uilabel 仅显示带有自动布局的一行