ansible
ansible

Reputation: 3579

UITableViewCell not wrapping text correctly when first loaded, but does when scrolled on and off the screen

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

Answers (2)

RyanJM
RyanJM

Reputation: 7068

For others coming by this page, Use Your Loaf has a couple articles about autolayout + UITableViewCells.

With iOS 7 and with iOS 8.

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

ansible
ansible

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

Related Questions