Reputation: 3579
I am following the answer from this thread about using a UITableViewCell with a dynamic height. Here is the link to the great GitHub solution for iOS7.
The example works in for me in iOS7, but when I run it in iOS6 the first cell(s) do not wrap correctly.
It's only after I scroll the cell of the screen and back on does the wrapping look correct.
Here is my code, with the following comment by any changes I made so it could run in iOS6
// *** Added/Removed for iOS6
The table view cell
#import "RJTableViewCell.h"
#define kLabelHorizontalInsets 20.0f
@interface RJTableViewCell ()
@property (nonatomic, assign) BOOL didSetupConstraints;
@end
@implementation RJTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
[self.titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.titleLabel setLineBreakMode:NSLineBreakByTruncatingTail];
[self.titleLabel setNumberOfLines:1];
[self.titleLabel setTextAlignment:NSTextAlignmentLeft];
[self.titleLabel setTextColor:[UIColor blackColor]];
[self.titleLabel setBackgroundColor:[UIColor clearColor]];
self.bodyLabel = [[UILabel alloc] initWithFrame:CGRectZero];
[self.bodyLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.bodyLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.bodyLabel setLineBreakMode:NSLineBreakByTruncatingTail];
[self.bodyLabel setNumberOfLines:0];
[self.bodyLabel setTextAlignment:NSTextAlignmentLeft];
[self.bodyLabel setTextColor:[UIColor darkGrayColor]];
[self.bodyLabel setBackgroundColor:[UIColor clearColor]];
[self.contentView addSubview:self.titleLabel];
[self.contentView addSubview:self.bodyLabel];
[self updateFonts];
}
return self;
}
- (void)updateConstraints
{
[super updateConstraints];
if (self.didSetupConstraints) return;
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.titleLabel
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:kLabelHorizontalInsets]];
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.titleLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:(kLabelHorizontalInsets / 2)]];
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.titleLabel
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:-kLabelHorizontalInsets]];
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.bodyLabel
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:kLabelHorizontalInsets]];
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.bodyLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.titleLabel
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:(kLabelHorizontalInsets / 4)]];
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.bodyLabel
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:-kLabelHorizontalInsets]];
[self.contentView addConstraint:[NSLayoutConstraint
constraintWithItem:self.bodyLabel
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:-(kLabelHorizontalInsets / 2)]];
self.didSetupConstraints = YES;
}
- (void)updateFonts
{
self.titleLabel2.font = [UIFont systemFontOfSize:16.0f]; // *** Added for iO6
self.bodyLabel.font = [UIFont systemFontOfSize:12.0f]; // *** Added for iO6
// self.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; // *** Removed for iO6
// self.bodyLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2]; // *** Removed for iO6
}
The table view controller
#import "RJTableViewController.h"
#import "RJModel.h"
#import "RJTableViewCell.h"
static NSString *CellIdentifier = @"CellIdentifier";
@interface RJTableViewController ()
@property (strong, nonatomic) RJModel *model;
// This property is used to work around the constraint exception that is thrown if the
// estimated row height for an inserted row is greater than the actual height for that row.
// See: https://github.com/caoimghgin/TableViewCellWithAutoLayout/issues/6
@property (assign, nonatomic) BOOL isInsertingRow;
@end
@implementation RJTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
self.title = @"Table View Controller";
self.model = [[RJModel alloc] init];
[self.model populateDataSource];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[RJTableViewCell class] forCellReuseIdentifier:CellIdentifier];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(clear:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addRow:)];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.tableView reloadData]; // *** Added for iOS6
// *** Removed for iOS6
// [[NSNotificationCenter defaultCenter] addObserver:self
// selector:@selector(contentSizeCategoryChanged:)
// name:UIContentSizeCategoryDidChangeNotification
// object:nil];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.tableView reloadData]; // *** Added for iOS6
// *** Removed for iOS6
// [[NSNotificationCenter defaultCenter] removeObserver:self
// name:UIContentSizeCategoryDidChangeNotification
// object:nil];
}
- (void)contentSizeCategoryChanged:(NSNotification *)notification
{
[self.tableView reloadData];
}
- (void)clear:(id)sender
{
NSMutableArray *rowsToDelete = [NSMutableArray new];
for (NSUInteger i = 0; i < [self.model.dataSource count]; i++) {
[rowsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
self.model = [[RJModel alloc] init];
[self.tableView deleteRowsAtIndexPaths:rowsToDelete withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView reloadData];
}
- (void)addRow:(id)sender
{
[self.model addSingleItemToDataSource];
self.isInsertingRow = YES;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:[self.model.dataSource count] - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[lastIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
self.isInsertingRow = NO;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.model.dataSource count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
RJTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
[cell updateFonts];
NSDictionary *dataSourceItem = [self.model.dataSource objectAtIndex:indexPath.row];
cell.titleLabel.text = [dataSourceItem valueForKey:@"title"];
cell.bodyLabel.text = [dataSourceItem valueForKey:@"body"];
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
[cell setNeedsUpdateConstraints];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
RJTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[cell updateFonts];
NSDictionary *dataSourceItem = [self.model.dataSource objectAtIndex:indexPath.row];
cell.titleLabel.text = [dataSourceItem valueForKey:@"title"];
cell.bodyLabel.text = [dataSourceItem valueForKey:@"body"];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Do the initial layout pass of the cell's contentView & subviews
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
// Since we have multi-line labels, set the preferredMaxLayoutWidth now that their width has been determined,
// and then do a second layout pass so they can take on the correct height
cell.bodyLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.bodyLabel.frame);
[cell.contentView layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.isInsertingRow) {
// A constraint exception will be thrown if the estimated row height for an inserted row is greater
// than the actual height for that row. In order to work around this, we return the actual height
// for the the row when inserting into the table view.
// See: https://github.com/caoimghgin/TableViewCellWithAutoLayout/issues/6
return [self tableView:tableView heightForRowAtIndexPath:indexPath];
} else {
return 500.0f;
}
}
@end
Here is a before and after of what I'm seeing if that is useful.
Any ideas what is going on? I appreciate any insight.
Upvotes: 1
Views: 1156
Reputation: 7068
For others coming by this page, Use Your Loaf has a couple articles about autolayout + UITableViewCell
s.
His code shows how to use a prototype cell to figure out height instead of dequing one every time. This will result in faster code.
By overwriting layoutSubviews
to set the preferredMaxLayoutWidth
it means you don't have to do it in your controller.
- (void)layoutSubviews
{
[super layoutSubviews];
[self.contentView layoutIfNeeded];
self.lineLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.lineLabel.frame);
}
[self.contentView layoutIfNeeded]
is also important to get the right layout when it first loads.
Upvotes: 0
Reputation: 3579
Per the advice from @Wain I tried moving all the code from heightForRowAtIndexPath
to cellForRowAtIndexPath
and that worked. I think I needed to set cell.bodyLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.bodyLabel.frame)
.
I moved all the code into 'cellForRowAtIndexPath' and have 'heightForRowAtIndexPath' calling it to get the height now. Now I'm not sure why it's fine in iOS7 and not iOS6,but I'm still figuring out some of this auto layout stuff.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
RJTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[cell updateFonts];
NSDictionary *dataSourceItem = [self.model.dataSource objectAtIndex:indexPath.row];
cell.titleLabel2.text = [dataSourceItem valueForKey:@"title"];
cell.bodyLabel.text = [dataSourceItem valueForKey:@"body"];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Do the initial layout pass of the cell's contentView & subviews
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
// Since we have multi-line labels, set the preferredMaxLayoutWidth now that their width has been determined,
// and then do a second layout pass so they can take on the correct height
cell.bodyLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.bodyLabel.frame);
[cell.contentView layoutIfNeeded];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
RJTableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
}
Upvotes: 1