UITableViewCell not autosizing - ios

I have two labels in a UITableViewCell subclass. I want both labels to be multiline labels, so I have set the number of lines to zero for both of them. But the problem is that my table view cell is not expanding. In fact, the labels are getting squished so much even though I have the compression resistance priority set to 1000 for both labels in both directions.
I have set the background of the firstLabel UILabel property to orange and the background of the secondLabel UILabel property to yellow to make it easier to see the labels. There is also a divider between each row to make it so the heights are easier to see.
Here is what the view looks like when run on a device. The labels are all compressed and the rows never increase in size no matter how much padding I add to the constraints or how much content I put in the labels. Additionally, the first label in the first row has enough content that it should be more than one line, but it is getting truncated with ....
Here is the code I am using for this. The first part is the table view cell subclass that has the two labels - firstLabel (the one on the left) and secondLabel (the one on the right). Next, I have the code for the view that has the table view and shows how the table view is configured. Finally, there is a UITableView subclass that sets the intrinsicContentSize of the table to be the content size of the table. I added this because without this the table frame always stayed at zero and so the table view data source methods were not getting called. If anyone knows a better way to do this too, that would be much appreciated.
UITableViewCell subclass
This is the implementation for my UITableViewCell. It is used programmatically, so I am
#interface MyTableViewCell ()
#property (strong, nonatomic) UILabel *firstLabel;
#property (strong, nonatomic) UILabel *secondLabel;
#end
#implementation MyTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self.contentView setBackgroundColor:[UIColor systemTealColor]];
[self.backgroundView setBackgroundColor:[UIColor systemGrayColor]];
[self addSubview:self.firstLabel];
[self addSubview:self.secondLabel];
NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.bottomAnchor constant:20.0f],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.bottomAnchor constant:20.0f],
heightConstraint
]];
}
- (UILabel *)firstLabel {
if (!self->_firstLabel) {
self->_firstLabel = [[UILabel alloc] init];
self->_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_firstLabel.numberOfLines = 0;
self->_firstLabel.userInteractionEnabled = NO;
self->_firstLabel.contentMode = UIViewContentModeScaleToFill;
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_firstLabel.textAlignment = NSTextAlignmentNatural;
self->_firstLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_firstLabel.adjustsFontSizeToFitWidth = NO;
self->_firstLabel.backgroundColor = [UIColor orangeColor];
}
return self->_firstLabel;
}
- (UILabel *)secondLabel {
if (!self->_secondLabel) {
self->_secondLabel = [[UILabel alloc] init];
self->_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_secondLabel.numberOfLines = 0;
self->_secondLabel.userInteractionEnabled = NO;
self->_secondLabel.contentMode = UIViewContentModeScaleToFill;
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_secondLabel.textAlignment = NSTextAlignmentNatural;
self->_secondLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_secondLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_secondLabel.adjustsFontSizeToFitWidth = NO;
self->_secondLabel.backgroundColor = [UIColor yellowColor];
}
return self->_secondLabel;
}
- (void)setData:(MyModel *)data {
self.firstLabel.text = data.first;
self.secondLabel.text = data.second;
[self invalidateIntrinsicContentSize];
}
#end
Primary View - has a table view as a child view
This is the actual view that is displayed. As part of the larger application, this view is then displayed in a vertical UIStackView.
This view has a table view as the only subview and the table view is pinned to the edges of this view with AutoLayout. The UITableView is actually an instance of another class called "AutosizingTableView" to get the table view to autosize (without this, the frame of the table stays at zero and the table view data source method, tableView:cellForRowAtIndexPath:, was never being called since the table height was zero. The code for this table view is included after this section.
#interface MyView ()
#property (strong, nonatomic) AutosizingTableView *tableView;
#end
#implementation MyView
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self addSubview:self.tableView];
[NSLayoutConstraint activateConstraints:#[
[self.tableView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[self.tableView.topAnchor constraintEqualToAnchor:self.topAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[self.tableView.widthAnchor constraintEqualToAnchor:self.widthAnchor]
]];
}
- (UITableView *)tableView {
if (!self->_tableView) {
self->_tableView = [[AutosizingTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self->_tableView.translatesAutoresizingMaskIntoConstraints = NO;
self->_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self->_tableView.rowHeight = UITableViewAutomaticDimension;
self->_tableView.estimatedRowHeight = UITableViewAutomaticDimension;
self->_tableView.allowsSelection = NO;
self->_tableView.scrollEnabled = NO;
self->_tableView.delegate = self;
self->_tableView.dataSource = self;
[self->_tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:#"myTableViewCell"];
}
return self->_tableView;
}
- (void)setdata:(NSArray<MyData *> *)data {
self->_data = data;
[self.tableView reloadData];
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myTableViewCell" forIndexPath:indexPath];
MyData *data = self.data[indexPath.row];
cell.detail = detail;
return cell;
}
#end
AutosizingTableView
As was mentioned in the previous section, this is just to make it so the table view autosizes. Without this, the table view height was zero and stayed at zero since the tableView:cellForRowAtIndexPath: method was never getting called because of the table view height.
#implementation AutosizingTableView
- (CGSize)intrinsicContentSize {
return self.contentSize;
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
[self invalidateIntrinsicContentSize];
}
#end

Try this:
- (void)initialize {
[self.contentView setBackgroundColor:[UIColor systemTealColor]];
[self.backgroundView setBackgroundColor:[UIColor systemGrayColor]];
[self.contentView addSubview:self.firstLabel];
[self.contentView addSubview:self.secondLabel];
NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.bottomAnchor constant:20.0f],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.bottomAnchor constant:20.0f],
heightConstraint
]];
}
You should be adding the labels to the contentView and no the cell directly.

Related

UILabel not showing all text

I have two labels in a UITableViewCell subclass. I want both labels to be multiline labels, so I have set the number of lines to zero for both of the labels. But the problem is that the labels keep getting truncated. For example, I have made the text size of the right label much larger so that it is taller but then I have added a lot of text to the first label on the left. But, rather than keep wrapping and adding more lines, it just truncates when it is the same size as the label on the right.
But the label on the left should have had a lot more text displayed and it should have taken several more lines, but instead it is truncated when it gets to the height of the other label. It is almost like I have a height constraint between the two labels, but I do not.
Here is the code I am using for this. The first part is the table view cell subclass that has the two labels - firstLabel (the one on the left) and secondLabel (the one on the right). Next, I have the code for the view that has the table view and shows how the table view is configured. Finally, there is a UITableView subclass that sets the intrinsicContentSize of the table to be the content size of the table. I added this because without this the table frame always stayed at zero and so the table view data source methods were not getting called. If anyone knows a better way to do this too, that would be much appreciated.
UITableViewCell subclass
This is the implementation for my UITableViewCell. I have vertical content hugging and content compression priorities for both labels, but I have tried this with and without these priorities and the result is the same.
#interface MyTableViewCell ()
#property (strong, nonatomic) UILabel *firstLabel;
#property (strong, nonatomic) UILabel *secondLabel;
#end
#implementation MyTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self.contentView setBackgroundColor:[UIColor systemTealColor]];
[self.backgroundView setBackgroundColor:[UIColor systemGrayColor]];
[self.contentView addSubview:self.firstLabel];
[self.contentView addSubview:self.secondLabel];
NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.bottomAnchor constant:20.0f],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.bottomAnchor constant:20.0f],
heightConstraint
]];
}
- (UILabel *)firstLabel {
if (!self->_firstLabel) {
self->_firstLabel = [[UILabel alloc] init];
self->_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_firstLabel.numberOfLines = 0;
self->_firstLabel.userInteractionEnabled = NO;
self->_firstLabel.contentMode = UIViewContentModeScaleToFill;
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_firstLabel.textAlignment = NSTextAlignmentNatural;
self->_firstLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_firstLabel.adjustsFontSizeToFitWidth = NO;
self->_firstLabel.backgroundColor = [UIColor orangeColor];
}
return self->_firstLabel;
}
- (UILabel *)secondLabel {
if (!self->_secondLabel) {
self->_secondLabel = [[UILabel alloc] init];
self->_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_secondLabel.numberOfLines = 0;
self->_secondLabel.userInteractionEnabled = NO;
self->_secondLabel.contentMode = UIViewContentModeScaleToFill;
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_secondLabel.textAlignment = NSTextAlignmentNatural;
self->_secondLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_secondLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_secondLabel.adjustsFontSizeToFitWidth = NO;
self->_secondLabel.backgroundColor = [UIColor yellowColor];
}
return self->_secondLabel;
}
- (void)setData:(MyModel *)data {
self.firstLabel.text = data.first;
self.secondLabel.text = data.second;
[self invalidateIntrinsicContentSize];
}
#end
Primary View - has a table view as a child view
This is the actual view that is displayed. As part of the larger application, this view is then displayed in a vertical UIStackView.
This view has a table view as the only subview and the table view is pinned to the edges of this view with AutoLayout. The UITableView is actually an instance of another class called "AutosizingTableView" to get the table view to autosize (without this, the frame of the table stays at zero and the table view data source method, tableView:cellForRowAtIndexPath:, was never being called since the table height was zero. The code for this table view is included after this section.
#interface MyView ()
#property (strong, nonatomic) AutosizingTableView *tableView;
#end
#implementation MyView
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self addSubview:self.tableView];
[NSLayoutConstraint activateConstraints:#[
[self.tableView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[self.tableView.topAnchor constraintEqualToAnchor:self.topAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[self.tableView.widthAnchor constraintEqualToAnchor:self.widthAnchor]
]];
}
- (UITableView *)tableView {
if (!self->_tableView) {
self->_tableView = [[AutosizingTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self->_tableView.translatesAutoresizingMaskIntoConstraints = NO;
self->_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self->_tableView.rowHeight = UITableViewAutomaticDimension;
self->_tableView.estimatedRowHeight = UITableViewAutomaticDimension;
self->_tableView.allowsSelection = NO;
self->_tableView.scrollEnabled = NO;
self->_tableView.delegate = self;
self->_tableView.dataSource = self;
[self->_tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:#"myTableViewCell"];
}
return self->_tableView;
}
- (void)setdata:(NSArray<MyData *> *)data {
self->_data = data;
[self.tableView reloadData];
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myTableViewCell" forIndexPath:indexPath];
MyData *data = self.data[indexPath.row];
cell.detail = detail;
return cell;
}
#end
AutosizingTableView
As was mentioned in the previous section, this is just to make it so the table view autosizes. Without this, the table view height was zero and stayed at zero since the tableView:cellForRowAtIndexPath: method was never getting called because of the table view height.
#implementation AutosizingTableView
- (CGSize)intrinsicContentSize {
return self.contentSize;
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
[self invalidateIntrinsicContentSize];
}
#end
A bit difficult to know for certain, because you didn't provide a complete working example, but this may fix your issue.
See the comments for changes:
#interface MyTableViewCell ()
#property (strong, nonatomic) UILabel *firstLabel;
#property (strong, nonatomic) UILabel *secondLabel;
#end
#implementation MyTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self.contentView setBackgroundColor:[UIColor systemTealColor]];
[self.backgroundView setBackgroundColor:[UIColor systemGrayColor]];
[self.contentView addSubview:self.firstLabel];
[self.contentView addSubview:self.secondLabel];
// not needed
//NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
//[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.bottomAnchor constant:20.0f],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.bottomAnchor constant:20.0f],
// not needed
//heightConstraint
]];
}
- (UILabel *)firstLabel {
if (!self->_firstLabel) {
self->_firstLabel = [[UILabel alloc] init];
self->_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_firstLabel.numberOfLines = 0;
self->_firstLabel.userInteractionEnabled = NO;
// not needed
//self->_firstLabel.contentMode = UIViewContentModeScaleToFill;
//[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
//[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
//[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
//[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_firstLabel.textAlignment = NSTextAlignmentNatural;
// word-wrapping, not truncating
//self->_firstLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_firstLabel.lineBreakMode = NSLineBreakByWordWrapping;
self->_firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_firstLabel.adjustsFontSizeToFitWidth = NO;
self->_firstLabel.backgroundColor = [UIColor orangeColor];
}
return self->_firstLabel;
}
- (UILabel *)secondLabel {
if (!self->_secondLabel) {
self->_secondLabel = [[UILabel alloc] init];
self->_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_secondLabel.numberOfLines = 0;
self->_secondLabel.userInteractionEnabled = NO;
// not needed
//self->_secondLabel.contentMode = UIViewContentModeScaleToFill;
//[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
//[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
//[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
//[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_secondLabel.textAlignment = NSTextAlignmentNatural;
// word-wrapping, not truncating
//self->_secondLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_secondLabel.lineBreakMode = NSLineBreakByWordWrapping;
self->_secondLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_secondLabel.adjustsFontSizeToFitWidth = NO;
self->_secondLabel.backgroundColor = [UIColor yellowColor];
}
return self->_secondLabel;
}
- (void)setData:(MyData *)data {
self.firstLabel.text = data.first;
self.secondLabel.text = data.second;
// this is needed
[self.firstLabel layoutIfNeeded];
[self.secondLabel layoutIfNeeded];
// not needed
//[self invalidateIntrinsicContentSize];
}
#end

When did UITableViewCell done layout subview?

As shown in the following image, I want to add a tip(subview of the biggest UIView) that left align with the UIStackView in UITableViewCell. The width of UIStackView in UITableViewCell is dynamic, so I need to place the tip view after the UITableViewCell layout its subviews.
I've tried to add the tip view in willDisplayCell function, but the position I get there is not the final position.
Also, I tried to set the tip view's position in the UITableViewCell's layoutSubviews function. Since this function is called multiple times, the tip will go to the expected place eventually, but it will flicker at the beginning.
So in which function should I add the tip view?
--------Edited--------
Add a code snippet to make it clearer. I just want to know where should I put the following code, so that rect.origin.x is the final position of the stackview.
UIView *view = self.view;
CGRect rect = [cell.stackView convertRect:cell.stackView.bounds toView:view];
[self.tipView.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:rect.origin.x].active = YES;
[self.tipView.trailingAnchor constraintLessThanOrEqualToAnchor:view.trailingAnchor constant:-marginConstant].active = YES;
[self.tipView.bottomAnchor constraintEqualToAnchor:self.tableView.bottomAnchor constant:-TSkBubbleClickAreaHeight-marginConstant].active = YES;
[view layoutIfNeeded];
Edit
To try and directly answer your question, you know when the layout of the content of the cell is completely finished when whatever you're doing to the cell is done. So, to know exactly when your cell's Stack View has been laid-out, you could subclass the stack view and implement layoutSubviews in that subclass.
Since it's still not clear exactly what you're doing, I'll offer a couple approaches...
If you are calling reloadData on the table view, you could try this:
[tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[self showTipView:pth];
});
If you are calling insertRowsAtIndexPaths, you could try this:
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[self showTipView:pth];
}];
[self->tableView insertRowsAtIndexPaths:#[pth] withRowAnimation:UITableViewRowAnimationRight];
[CATransaction commit];
Here's an example (I've tried to keep it simple, and provide clear comments):
ExampleTableViewCell.h
//
// ExampleTableViewCell.h
// Created by Don Mag on 9/15/20.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface ExampleTableViewCell : UITableViewCell
- (void) configureCell:(NSString *)s;
- (UIStackView *)getStackRef;
#end
NS_ASSUME_NONNULL_END
ExampleTableViewCell.m
//
// ExampleTableViewCell.m
// Created by Don Mag on 9/15/20.
//
#import "ExampleTableViewCell.h"
#interface ExampleTableViewCell ()
{
UIStackView *stackView;
UILabel *label;
}
#end
#implementation ExampleTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self commonInit];
}
return self;
}
- (void) commonInit {
label = [UILabel new];
label.backgroundColor = [UIColor yellowColor];
stackView = [UIStackView new];
stackView.translatesAutoresizingMaskIntoConstraints = NO;
[stackView addArrangedSubview:label];
[self.contentView addSubview:stackView];
UILayoutGuide *g = self.contentView.layoutMarginsGuide;
[NSLayoutConstraint activateConstraints:#[
// constrain stack view Top: 0 / Trailing: 0 / Bottom: 0
[stackView.topAnchor constraintEqualToAnchor:g.topAnchor constant:0.0],
[stackView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
[stackView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
]];
}
- (void) configureCell:(NSString *)s {
label.text = s;
}
- (UIStackView *)getStackRef {
return stackView;
}
#end
ExampleViewController.h
//
// ExampleViewController.h
// Created by Don Mag on 9/15/20.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface ExampleViewController : UIViewController
#end
NS_ASSUME_NONNULL_END
ExampleViewController.m
//
// ExampleViewController.m
// Created by Don Mag on 9/15/20.
//
#import "ExampleViewController.h"
#import "ExampleTableViewCell.h"
#interface ExampleViewController () <UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>
{
NSMutableArray *myData;
NSMutableArray *sampleStrings;
UIView *tipView;
UIButton *reloadButton;
UIButton *insertButton;
UITableView *tableView;
NSInteger idx;
NSInteger insertRow;
}
#end
#implementation ExampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
// row number to insert and show the tipView
insertRow = 7;
// init some sample data
myData = [NSMutableArray new];
for (int i = 0; i < 30; i++) {
NSString *s = [NSString stringWithFormat:#"Row %i", i];
[myData addObject:s];
}
// a few example strings
sampleStrings = [NSMutableArray new];
[sampleStrings addObject:#"Short text example."];
[sampleStrings addObject:#"A little more text example."];
[sampleStrings addObject:#"Considerably longer text for this example."];
// index for sampleStrings array
idx = -1;
// create a "tip view"
// red background view with
// green background label
// label is inset 4-pts on each side
tipView = [UIView new];
tipView.backgroundColor = [UIColor redColor];
UILabel *tipLabel = [UILabel new];
tipLabel.backgroundColor = [UIColor greenColor];
tipLabel.text = #"This is the tip!";
tipView.translatesAutoresizingMaskIntoConstraints = NO;
tipLabel.translatesAutoresizingMaskIntoConstraints = NO;
[tipView addSubview:tipLabel];
[NSLayoutConstraint activateConstraints:#[
[tipLabel.topAnchor constraintEqualToAnchor:tipView.topAnchor constant:4.0],
[tipLabel.leadingAnchor constraintEqualToAnchor:tipView.leadingAnchor constant:4.0],
[tipLabel.bottomAnchor constraintEqualToAnchor:tipView.bottomAnchor constant:-4.0],
[tipLabel.trailingAnchor constraintEqualToAnchor:tipView.trailingAnchor constant:-4.0],
]];
// init buttons
reloadButton = [UIButton new];
[reloadButton setTitle:#"Reload" forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
[reloadButton setBackgroundColor:[UIColor blueColor]];
insertButton = [UIButton new];
[insertButton setTitle:#"Insert" forState:UIControlStateNormal];
[insertButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[insertButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
[insertButton setBackgroundColor:[UIColor blueColor]];
// init table view
tableView = [UITableView new];
for (UIView *v in #[reloadButton, insertButton, tableView]) {
v.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:v];
}
UILayoutGuide *g = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:#[
// buttons at top
[reloadButton.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
[reloadButton.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:24.0],
[insertButton.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
[insertButton.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-24.0],
[insertButton.leadingAnchor constraintEqualToAnchor:reloadButton.trailingAnchor constant:20.0],
[insertButton.widthAnchor constraintEqualToAnchor:reloadButton.widthAnchor],
// constrain tableView Top: 20-pts from buttons / Leading: 0 / Trailing: 0 / Bottom: 0
[tableView.topAnchor constraintEqualToAnchor:reloadButton.bottomAnchor constant:20.0],
[tableView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
[tableView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
[tableView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
]];
[tableView registerClass:[ExampleTableViewCell class] forCellReuseIdentifier:#"exCell"];
tableView.delegate = self;
tableView.dataSource = self;
[reloadButton addTarget:self action:#selector(btnTap:) forControlEvents:UIControlEventTouchUpInside];
[insertButton addTarget:self action:#selector(btnTap:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[tipView removeFromSuperview];
}
- (void)btnTap:(UIButton *)btn {
// remove tipView if it's showing
[tipView removeFromSuperview];
NSString *s = sampleStrings[++idx % 3];
if (btn == reloadButton) {
[self reloadMethod:s];
} else {
[self insertMethod:s];
}
}
- (void)insertMethod:(NSString *)s {
// IndexPath for cell you want
__block NSIndexPath *pth = [NSIndexPath indexPathForRow:insertRow inSection:0];
[myData insertObject:s atIndex:pth.row];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[self showTipView:pth];
}];
[self->tableView insertRowsAtIndexPaths:#[pth] withRowAnimation:UITableViewRowAnimationRight];
[CATransaction commit];
}
- (void)reloadMethod:(NSString *)s {
// IndexPath for cell you want
__block NSIndexPath *pth = [NSIndexPath indexPathForRow:insertRow inSection:0];
[myData insertObject:s atIndex:pth.row];
[tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[self showTipView:pth];
});
}
- (void)showTipView:(NSIndexPath *)pth {
// get a reference to the cell
ExampleTableViewCell *cell = [tableView cellForRowAtIndexPath:pth];
// if row is not visible
if (!cell) {
return;
}
// get a reference to the cell's stack view
UIStackView *stack = [cell getStackRef];
// add tipView to self.view
[self.view addSubview:tipView];
// constrain tipView
// Leading to stack view's Leading
// Bottom to cell's Top - 4
[NSLayoutConstraint activateConstraints:#[
[tipView.leadingAnchor constraintEqualToAnchor:stack.leadingAnchor constant:0.0],
[tipView.bottomAnchor constraintEqualToAnchor:cell.topAnchor constant:-4.0],
]];
// if we want to "fade-in" the tipView
[tipView setAlpha:0.0];
[UIView animateWithDuration:0.3
animations:^{
[self->tipView setAlpha:1.0];
}];
}
- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
ExampleTableViewCell *c = (ExampleTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"exCell" forIndexPath:indexPath];
[c configureCell:myData[indexPath.row]];
return c;
}
- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [myData count];
}
#end

Adding Subview to UICollectionViewCell Subclass?

I seem to be having trouble adding a subview to a view within my UICollectionViewCell subclass.
I have an abstract UICollectionViewCell subclass titled MessageItem, which looks like this:
I've created a few classes that inherit from this (since they all use the same logic for the header and footer). However I can't seem to add any subviews into MessageItem's blue view from within the child subclasses.
For example one of the child views is called TextItem. I'm trying to add a label to it's parent messageView (the blue view) but it only works if I do it in my UIViewController's cellForItemAtIndexPath:(NSIndexPath *)indexPath method, and not in my custom subclass.
This is how I'm trying to add it in my child subclass:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//Setup Message Label
[self setupMessageLabel];
}
return self;
}
#pragma mark - Setup Methods
- (void)setupMessageLabel {
NSLog(#"Setting up label");
//Setup Message Label
self.messageLabel = [TTTAttributedLabel new];
self.messageLabel.verticalAlignment = TTTAttributedLabelVerticalAlignmentCenter;
self.messageLabel.textInsets = UIEdgeInsetsMake(8, 8, 8, 8);
self.messageLabel.numberOfLines = 0;
[self.messageContentView addSubview:self.messageLabel];
[self.messageContentView autoPinEdgesToSuperviewEdges];
//Update Label Color
self.messageLabel.backgroundColor = FlatRed;
}
Note: I'm not using storyboard or xibs. Could that be the problem?
Update
This is what my MessageItem class is implemented:
MessageItem.h
#import <UIKit/UIKit.h>
#class Message;
#interface MessageItem : UICollectionViewCell
#property (nonatomic, strong) Message *message;
#property (nonatomic, strong) UIView *messageContentView;
#end
MessageItem.m
#interface MessageItem ()
#property (nonatomic, strong) TTTAttributedLabel *headerLabel;
#property (nonatomic, strong) TTTAttributedLabel *footerLabel;
#end
#implementation MessageItem
#synthesize message = _message;
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//Setup Main View
[self setupMainView];
}
return self;
}
#pragma mark - Setup Methods
- (void)setupMainView {
//Setup Header
[self setupHeaderLabel];
//Setup Message
[self setupMessageView];
//Setup Footer View
[self setupFooterLabel];
}
- (void)setupHeaderLabel {
//Setup Header Label
self.headerLabel = [[TTTAttributedLabel alloc] initForAutoLayout];
self.headerLabel.font = [UIFont fontWithName:#"Lato-Bold" size:12.0];
self.headerLabel.textColor = FlatGray;
self.headerLabel.textAlignment = NSTextAlignmentCenter;
self.headerLabel.verticalAlignment = TTTAttributedLabelVerticalAlignmentCenter;
self.headerLabel.textInsets = UIEdgeInsetsMake(0, 8, 0, 8);
self.headerLabel.backgroundColor = FlatPurple;
[self.contentView addSubview:self.headerLabel];
[self.headerLabel autoSetDimension:ALDimensionHeight toSize:20.0];
[self.headerLabel autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeBottom];
}
- (void)setupMessageView {
//Setup Message View
self.messageContentView = [UIView new];
self.messageContentView.backgroundColor = [UIColor blueColor];
[self.contentView addSubview:self.messageContentView];
[self.messageContentView autoSetDimension:ALDimensionHeight toSize:30 relation:NSLayoutRelationGreaterThanOrEqual];
[self.messageContentView autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[self.messageContentView autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
[self.messageContentView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.headerLabel];
}
- (void)setupFooterLabel {
//Setup Footer Label
self.footerLabel = [[TTTAttributedLabel alloc] initForAutoLayout];
self.footerLabel.font = [UIFont fontWithName:#"Lato-Bold" size:10.0];
self.footerLabel.textColor = FlatGray;
self.footerLabel.backgroundColor = FlatGreen;
self.footerLabel.textAlignment = NSTextAlignmentLeft;
self.footerLabel.textInsets = UIEdgeInsetsMake(0, 8, 0, 8);
[self.contentView addSubview:self.footerLabel];
[self.footerLabel autoSetDimension:ALDimensionHeight toSize:10.0];
[self.footerLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[self.footerLabel autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
[self.footerLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[self.footerLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.messageContentView];
}
TextItem.m
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//Setup Message Label
[self setupMessageLabel];
}
return self;
}
#pragma mark - Setup Methods
- (void)setupMessageLabel {
//Setup Message Label
self.messageLabel = [[TTTAttributedLabel alloc] initWithFrame:CGRectMake(0, 0, 320, 100)];
self.messageLabel.verticalAlignment = TTTAttributedLabelVerticalAlignmentCenter;
self.messageLabel.textInsets = UIEdgeInsetsMake(8, 8, 8, 8);
self.messageLabel.numberOfLines = 0;
[self.messageContentView addSubview:self.messageLabel];
//Update Label Color
self.messageLabel.backgroundColor = FlatRed;
}
#pragma mark - Setter Methods
- (void)setMessageText:(NSString *)text {
//Incoming Text Message
NSMutableAttributedString *textString = [[NSMutableAttributedString alloc] initWithString:text];
[textString addAttribute:NSForegroundColorAttributeName value:[UIColor darkGrayColor] range:NSMakeRange(0, textString.length)];
[textString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16 weight:UIFontWeightLight] range:NSMakeRange(0, textString.length)];
//Set Paragraph Style
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.minimumLineHeight = 20;
paragraphStyle.maximumLineHeight = 20;
[textString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, textString.length)];
//Update Message Label
[self.messageLabel setText:textString];
NSLog(#"Set Message Label Text");
}
- (void)setMessage:(Message *)message {
//Super
[super setMessage:message];
//Update Message Text
[self setMessageText:message.text];
}
This is what my collectionView looks like:
I would at least expect the color of the messageLabel to reflect the change in TextItem, but it doesn't.
Have you implement initWithCoder?
- (id)initWithCoder:(NSCoder*)aDecoder
{
if(self = [super initWithCoder:aDecoder]) {
// Do something
}
return self;
}
I don't have all your code, but you code looks good to me. Maybe the problem was how you init the TextItem.
Here is a demo using your code, it works fine to me. https://www.dropbox.com/s/7qp9ayqnyacf57j/CustomCellView.zip?dl=0

Misplaced view warning and odd behavior with IB_DESIGNABLE custom view using auto layout

I create a custom IB-designable view (see code below) which renders correctly in IB and also works fine when running it. However, in get this warning about the view being misplaced and I cannot manually resize the view in Interface Builder (when touching a resize handle, the view will jump around in its container).
I get the same or similar behavior for all kinds of different layouts. Do you have an idea if I'm doing something wrong here, or is this just a bug in IB?
(PS: I cannot just ignore the warning)
EDIT: added screenshot of constraints:
Here is the code (header):
IB_DESIGNABLE
#interface AKATestView : UIView
#end
Implementation:
#interface AKATestView()
#property(nonatomic)BOOL subviewsCreated;
#property(nonatomic)BOOL subviewConstraintsCreated;
#property(nonatomic)NSDictionary* views;
#end
#implementation AKATestView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setupAfterInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setupAfterInit];
}
return self;
}
- (void)setupAfterInit
{
[self createSubviews];
}
- (void)createSubviews
{
if (!self.subviewsCreated)
{
self.translatesAutoresizingMaskIntoConstraints = NO;
UILabel* labelView = [[UILabel alloc] initWithFrame:CGRectZero];
labelView.text = #"Name";
labelView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:labelView];
UITextField* textField = [[UITextField alloc] initWithFrame:CGRectZero];
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.placeholder = #"Enter some text";
textField.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:textField];
UILabel* errorMessageLabel = [[UILabel alloc] initWithFrame:CGRectZero];
errorMessageLabel.text = #"Error message";
errorMessageLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:errorMessageLabel];
self.views = #{ #"label": labelView, #"editor": textField, #"errorMessageLabel": errorMessageLabel };
self.subviewsCreated = YES;
[self setNeedsUpdateConstraints];
}
}
- (void)updateConstraints
{
if (!self.subviewConstraintsCreated)
{
NSDictionary* metrics =
#{ #"pt": #(4), #"pr": #(4), #"pb": #(4), #"pl": #(4),
#"labelWidth": #(100),
#"errorPl": #(4 + 100 + 4),
#"hsLabelEditor": #(4), #"vsEditorError": #(2)
};
NSArray* specs =
#[ #{ #"format": #"H:|-(pl)-[label(labelWidth)]-(hsLabelEditor)-[editor]-(pr)-|",
#"options": #(NSLayoutFormatAlignAllFirstBaseline) },
#{ #"format": #"V:|-(pt)-[editor]-(vsEditorError)-[errorMessageLabel]-(pb)-|",
#"options": #(NSLayoutFormatAlignAllLeading|NSLayoutFormatAlignAllTrailing) }
];
for (NSDictionary* spec in specs)
{
NSString* format = spec[#"format"];
NSUInteger options = ((NSNumber*)spec[#"options"]).unsignedIntegerValue;
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:format
options:options
metrics:metrics
views:self.views];
[self addConstraints:constraints];
}
self.subviewConstraintsCreated = YES;
}
[super updateConstraints];
}
#end
Try removing self.translatesAutoresizingMaskIntoConstraints = NO; in your createSubviews method. IB seems to be relying on this translation to come up with correct measurement on the designer. I had the exact same problem and this fixed it.
I still have translatesAutosizingMaskIntoConstraints to NO for subviews. I confirmed that there aren't any extra constraints generated even with this set to YES. Hope it's the case for you too!

Autolayout : Resize view animation does not work

I am working now on custom UITextField, my main goal is to deliver custom placeholder animation. I want simply resize the placeholder and move it to top left corner. In gif bellow you can see move to top left works well, but resize is not animated and I have no idea way. Both of those actions are animated the same way with auto layout. Base on what I read it should works, and works for any other animation within auto layout, exclude view resize.
Thoughts / comments? What the heck am I doing wrong?
My current implementation:
#import "LTTextField.h"
#import "PureLayout.h"
#import <QuartzCore/QuartzCore.h>
#interface LTTextField()<UITextFieldDelegate>
#property (nonatomic, strong) UILabel *betterPlaceholder;
#property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
#end
#implementation LTTextField
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
[self setBorderStyle:UITextBorderStyleNone];
self.delegate = self;
self.betterPlaceholder = [[UILabel alloc] initForAutoLayout];
[self.betterPlaceholder setFont:[UIFont systemFontOfSize:17.0f]];
[self.betterPlaceholder setAdjustsFontSizeToFitWidth:YES];
[self.betterPlaceholder setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
[self addSubview:self.betterPlaceholder];
[self.betterPlaceholder autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:self];
[self.betterPlaceholder autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:self];
[self.betterPlaceholder autoMatchDimension:ALDimensionHeight toDimension:ALDimensionWidth ofView:self.betterPlaceholder];
self.heightConstraint = [self.betterPlaceholder autoSetDimension:ALDimensionHeight toSize:CGRectGetHeight(self.frame)];
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
[self addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}
return self;
}
- (void)drawPlaceholderInRect:(CGRect)rect {}
- (void)awakeFromNib{
[super awakeFromNib];
[self refreshPlaceHolderText];
}
- (void)refreshPlaceHolderText{
if (self.placeholder) {
if (self.attributedPlaceholder) {
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedPlaceholder];
[attributedString setAttributes:#{NSForegroundColorAttributeName: [UIColor blueColor]} range:NSMakeRange(0, attributedString.string.length)];
[self.betterPlaceholder setAttributedText:attributedString];
} else {
[self.betterPlaceholder setText:self.placeholder];
}
NSLog(#"Placeholder text %#",self.placeholder);
}
}
- (void)animatePlaceholderToState:(LTPlaceholderState)state animated:(BOOL)animated {
if (LTPlaceholderStateStart == state) {
self.heightConstraint.constant = CGRectGetHeight(self.frame);
} else if (LTPlaceholderStateEnd == state) {
self.heightConstraint.constant = 20;
}
[UIView animateKeyframesWithDuration:1.0f
delay:0.0f
options:UIViewKeyframeAnimationOptionBeginFromCurrentState
animations:^{
[self setNeedsLayout];
[self layoutIfNeeded];
} completion:nil];
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidChange:(UITextField *)theTextField{
LTPlaceholderState placeholderState;
if( theTextField.text.length > 0 ) {
placeholderState = LTPlaceholderStateEnd;
} else {
placeholderState = LTPlaceholderStateStart;
}
[self animatePlaceholderToState:placeholderState animated:self.editing];
}
#end
It looks like your animation is working fine, and it's just that the font size snaps to the smaller font. You're forcing the width and height of the label to be the same with your use of autoMatchDimension, and you've requested setAdjustsFontSizeToFitWidth:YES. Ergo when the new layout is computed, the font size is reduced (or increased, depending on whether it's animating in or out). Font size is not an animatable property, so the change happens immediately.
You might have some luck in not changing the view frame height, but instead animating a change in both the scale and frame origin of the view. This will ensure it gets smaller, but doesn't actually require animating text attribute changes.

Resources