Reputation: 1542
I am trying to create a reusable message UIView
subclass that adjusts its height based on the text inside its UILabel
. This answer says it can't be done. Is that really the case? iOS message cell width/height using auto layout
My issue is that the UILabel's CGFrame
's height is too large. (The UILabel
has a green background color.)
Here's my code (by the way, [autolayoutView]
sets translatesAutoresizingMaskIntoConstraints
to 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;
}
Upvotes: 1
Views: 2604
Reputation: 2005
Make your superview aware of changes of its content's size and adjust the superview's width and height accordingly. Since it may not be immediately obvious how to do this I have provided a UIView subclass that will resize itself based on its content (a UILabel).
Note: Only add constraints to ReusableMessageView that would affect its location within its superview. ReusableMessageView will adjust its width/height based on the message.
@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
This could be improved by reusing existing constraints and only changing the "constant" property of each constraint. You could even animate the change if you did this.
Here is an example use inside of a viewController's viewDidLoad method:
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"];
});
Upvotes: 1
Reputation: 534893
Of course it can be done, but not without code. Views do not automatically make themselves smaller based on the constraints and sizes of what's inside them. That's not how auto layout behaves.
You can determine the size that something needs to be based on the constraints and sizes of what's inside them (using systemLayoutFittingSize
) and you can set that thing to that size in code. But it isn't going to happen by some kind of magic.
Upvotes: 0