Search code examples
iosobjective-cuitableviewautolayoutnslayoutconstraint

UITableViewCell not autosizing


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 ....

enter image description here

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

Solution

  • 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.