2

I am creating an app that I want to run under both iOS7 and iOS8, that has variable table cell heights. In iOS7 I used to define a prototype cell and then use this in heightForRowAtIndexPath to calculate the height, based on the size of the text placed into labels.

This no longer seems to work when using Xcode 6. I created a small test app today to see if I was going crazy (maybe I am). I started by NOT defining heightForRowAtIndexPath at all. When I test it with iOS7 and iOS8 devices it behaves differently. In the iOS8 device it automatically determines the cell height and works fine. In iOS7 the cells are returned all the same height. It doesn't do the automatic height adjustment for the cells with variable text, for iOS7, only for iOS8.

So... me thinking... hmmm... under iOS8 I can use auto layout to determine the height and only implement heightForRowAtIndexPath for iOS7...

Okay, so... I tried adding in heightForRowAtIndexPath. Under iOS7 it crashes on method systemLayoutSizeFittingSize. I played with this for hours and nothing I do seems to change this. It is extremely simple code.

Here is the UITableViewController code:

//
//  TestTableViewController.m
//  testTables
//

#import "TestTableViewController.h"
#import "Cell1.h"

@interface TestTableViewController ()

@property (nonatomic, strong) Cell1 *cellPrototype;

@end

@implementation TestTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tableView registerNib:[UINib nibWithNibName:@"Cell1" bundle:nil] forCellReuseIdentifier:@"Cell1"];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    return 10;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    self.cellPrototype = [self.tableView dequeueReusableCellWithIdentifier:@"Cell1"];
    self.cellPrototype.label1.text = [self cellContents:indexPath];

    [self.cellPrototype layoutIfNeeded];
    CGSize size = [self.cellPrototype systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    return size.height;

}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Cell1 *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell1" forIndexPath:indexPath];

    cell.label1.text = [self cellContents:indexPath];
    return cell;
}

- (NSString*)cellContents:(NSIndexPath *)indexPath  {

    if (indexPath.row == 0) {
        return @"one line";
    }
    else if (indexPath.row == 1) {
        return @"two lines I think will result from this piece of text that goes over";
    }
    else if (indexPath.row == 2) {
        return @"three lines I think will result from this piece of text that goes over and then even extends a little more again";
    }
    else {
        return @"three lines I think will result from this piece of text that goes over and then even extends a little more again, and then extends over more and more lines like it will go on forever and never end but in fact it does end right here.";
    }

}

@end

Here is the cell header (there is nothing in the .m file):

//
//  Cell1.h
//  testTables
//
#import <UIKit/UIKit.h>

@interface Cell1 : UITableViewCell
@property (weak, nonatomic) IBOutlet UILabel *label1;

@end

Here is the xib for the cell. As you can see, it's not complex: enter image description here

So... does this old method not work any more? How can I get variable height cells that will run in both iOSes?

Darren
  • 1,417
  • 13
  • 23
  • It looks like you're doing things right except for maybe a couple issues. Shouldn't you be calling systemLayoutSizeFittingSize on the cell's content view? Shouldn't your prototype be created once and reused instead of dequeuing it from the table? – CrimsonChris Nov 04 '14 at 23:38
  • @CrimsonChris Thanks. Yes, I tried both of those things and nothing made any difference. I tried doing systemLayoutSizeFittingSize on the contentView and also tried different methods of instantiating the prototype and all had the same result. All fail on systemLayoutSizeFittingSize, complaining about constraints. – Darren Nov 04 '14 at 23:45
  • When you set the target to iOS7 does interface builder give you any warnings. Auto layout constraints were changed in interface builder with Xcode 6. You should see warnings if you are using constraints that aren't backwards compatible. – CrimsonChris Nov 05 '14 at 00:07
  • yes, one warning - "automatic preferred max layout width" is not available prior to iOS8. But when I change this by checking on explicit in IB it makes no difference. The warning disappears but it still gives a mass of constraints errors. – Darren Nov 05 '14 at 00:26
  • Fixing those errors should resolve your crash. – CrimsonChris Nov 05 '14 at 01:33
  • Yeah, I tried that too. So I removed the bottom constraint and it doesn't crash any more. But it returns 0,0 for the size (CGSize size). – Darren Nov 05 '14 at 19:45
  • Don't forget to set the preferred max layout width manually. – CrimsonChris Nov 05 '14 at 20:01
  • Removing the bottom constraint is not the solution. You should have constraints on the top, bottom, left, and right of your label. What errors are you getting that is preventing this? – CrimsonChris Nov 05 '14 at 21:08
  • Yes, manually setting the preferred max layout width solved it. Chris can you post this as the answer so you get the credit? thanks! – Darren Nov 15 '14 at 19:52

1 Answers1

1

Many of the warnings you are describing are explained in detail here. Automatic Preferred Max Layout Width is not available on iOS versions prior to 8.0

Prior to iOS 8, the preferredMaxLayoutWidth needed to be set manually. This property can be set anywhere if it's unlikely to change but my preferred solution is to use a subclassed UILabel that automatically sets the preferredMaxLayoutWidth.

Your subclassed UILabel would override layoutSubviews so that its preferredMaxLayoutWidth is always the same as its width.

- (void)layoutSubviews {
    self.preferredMaxLayoutWidth = self.bounds.size.width;
    [super layoutSubviews];
}
Community
  • 1
  • 1
CrimsonChris
  • 4,651
  • 2
  • 19
  • 30