4

I can't seem to get AutoLayout working on my Table View Cells.

On some cells it seems to work, and on others it seems to not work. Even cells of the exact same kind.

For example, on some cells the Description will be more than 1 lines worth of text and it will work correctly...

...Yet on other cells the Description will be more than 1 lines worth of text but only show 1 line of it with a bunch of empty space.

Can you help me figure out what I'm missing or doing wrong? Thanks!

I'm using this StackOverflow question to guide my process as a first-timer doing this: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

1. Set Up & Add Constraints

These are working well for the most part I believe.

2. Determine Unique Table View Cell Reuse Identifiers

I'm not totally sure if I need to worry about this part since I will always have a Headline, Time, and Description.

For iOS 8 - Self-Sizing Cells 3. Enable Row Height Estimation

I added this to viewDidLoad:

self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 180.0;

UPDATE: Adding more info per Acey request enter image description here

To be clear, I put constraints:

  • Headline: 15 left, 85 top, 15 right
  • Vertical Spacing between Headline and Time, of 10
  • Vertical Spacing between Time and Description, of 10
  • I Cmd clicked all three labels and added Leading Edges and Trailing Edges
  • I pinned 20 between Description and the bottom of the Table View Cell

UPDATE 2: Solved Answer below worked really well, but also any extra spacing was due to height set for cell being too large, so Xcode was automatically adding extra space to fill out height of cell since text labels didn't fill out the full height of the Table View Cell.

Let me know if you have any questions or need any help on this if you come across this and have the same problem.

Thanks everyone!

Community
  • 1
  • 1
Realinstomp
  • 532
  • 2
  • 13
  • 30
  • If you are trying to target iOS 8 and up only and can get your constraints right, you may omit the `tableView:heightForRowAtIndexPath:` method. It seems like you may be having issues with creating the constraints. Can you post a screenshot of all of the constraints for the cell from the left side in IB? (I can only presume this is the area you want help with - you haven't really asked any specific questions). – Acey Sep 30 '14 at 00:17
  • Thanks for the response! I updated my question at the bottom to show the screenshot you were wanting to check out. Let me know if that was what you were looking for or not. My main question is, I'm trying to get this cell to resize based on the contents, but its not working, so can you help me figure out what I'm missing? Thanks! – Realinstomp Sep 30 '14 at 00:27
  • @Acey did the screenshots I posted help make it clearer? Thanks for the help! – Realinstomp Sep 30 '14 at 22:17
  • @Reez : Do you care if it's autolayout? I have worked with cells a lot and have also had issues with auto layout, so I just made them dynamic instead (using self.view.bounds). If you don't care about using auto layout I can post an answer below – Chris Oct 06 '14 at 18:23
  • @Chris sure that'd be just fine! – Realinstomp Oct 06 '14 at 23:18

2 Answers2

3

I haven't tried using the new iOS 8 mechanisms yet. But I have faced similar issues when I was doing this with iOS 6 / 7. After updating the app to iOS 8 it still works fine, so maybe the old way is still the best way?

I have some examples of my code here: AutoLayout multiline UILabel cutting off some text

And here: AutoLayout uitableviewcell in landscape and on iPad calculating height based on portrait iPhone

Long story short the pre iOS 8 way involved keeping a copy of a cell just for calculating the height inside tableView:heightForRowAtIndexPath:. But this wasn't enough for dealing with multi line UILabel's. I had to subclass UILabel to update the preferredMaxLayoutWidth every time layoutSubviews was called.

The preferredMaxLayoutWidth "fix" seemed to be the magic secret I was missing. Once I did this most of my cells worked perfectly.

The second issue I had only required me to set the content compression resistance and content hugging properties correctly, so for example telling the label to hug the text will mean it won't expand to fill the whitespace which will cause the cell to shrink.

Once I did these 2 things my cells now handle any font size, or any amount of text without any messy layout code. It was a lot to learn but I do think it paid off in the end, as I have a lot of dynamic content in my app.

Edit

After coming across a few issues of my own with iOS 8, i'm adding some more details to solve these very odd autoLayout bugs.

With the code I mentioned, it doesn't seem to work when the cell "Row Height" is not set to custom. This setting is found in IB by selecting the cell and clicking the autoLayout tab (where all the content compression resistance settings etc are). Press the checkbox and it will fill with a temporary height.

Second is, in my code I keep a local copy of a cell, and then reuse it many times inside the heightForRowAtIndexPath: method. This seems to increase the cell height by a lot every time it is called. I had to re-init the local copy by calling:

localCopy = [self.tableView dequeueReusableCellWithIdentifier:@"mycell"];

It appears the new Xcode 6 / iOS 8 changes are very much so not backwards compatible with iOS 7 and it seems to be managed quite differently.

Hope this helps.

Edit 2

after reading this question: iOS AutoLayout multi-line UILabel

I've come across another issue with iOS 7 / iOS 8 autolayout support!!! I was overriding layoutSubviews for iOS 8 I also needed to override setBounds to update the preferredMaxLayoutWidth after calling super. WTF have apple changed!

Seems to be an issue with the setting in IB for preferredMaxLayoutWidth, because iOS 7 can't use the automatic feature, if you use the same UILabel on multiple devices, its only going to use the 1 width. So UITableViewCell's on an iOS 8 tablet will be bigger because the same cell needs to have 2 lines on an iOS 8 iPhone.

Community
  • 1
  • 1
Simon McLoughlin
  • 8,293
  • 5
  • 32
  • 56
1

Here is my attempt.

You could create a method/function that get's you the cellview that you need. Like so:

- (UIView *) getCellView {

    UIView *cellView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, 0.0f)];
    cellView.tag = 1;
    cellView.backgroundColor = [UIColor clearColor];

    UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(15.0f, 10.0f, 15.0f, 15.0f)]; //I assumed that was the size of your imageView;
    imgView.image = [UIImage imageNamed:@"whatever-your-image-is-called"];
    [cellView addSubview:imgView];

    CGFloat xPadding = 15.0f;
    CGFloat yPadding = 15.0f;
    UILabel *headlineLabel = [[UILabel alloc ]initWithFrame:CGRectMake(xPadding, 0.0f, self.view.frame.size.width - (xPadding*2), 0.0f)];
    headlineLabel.numberOfLines = 0;
    headlineLabel.text = @"Red Sox season fell apart after World Series title (The Associated Press)";
    [headlineLabel sizeToFit];
    CGRect hFrame = headlineLabel.frame;
    if(hFrame.size.width > self.view.frame.size.width - 31.0f)
        hFrame.size.width = self.view.frame.size.width - 30.0f;
    hFrame.origin.y = imgView.frame.size.height + yPadding;
    headlineLabel.frame = hFrame;

    UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(xPadding, 0.0f, self.view.frame.size.height-(xPadding*2), 0.0f)];
    timeLabel.text = @"4h";
    //timeLabel.numberOfLines = 0; //uncomment if it will wrap on multiple lines;
    [timeLabel sizeToFit];
    hFrame = timeLabel.frame;
    if(hFrame.size.width > self.view.frame.size.width - 31.0f)
        hFrame.size.width = self.view.frame.size.width - 30.0f;
    hFrame.origin.y = headlineLabel.frame.size.height + yPadding;
    timeLabel.frame = hFrame;

    UILabel *descriptLabel = [[UILabel alloc] initWithFrame:CGRectMake(xPadding, 0.0f, self.view.frame.size.height - (xPadding*2), 0.0f)];
    descriptLabel.text = @"Boston (AP) -- To Boston Red Sox manager John Farrel, it hardly seems possible that just 11 months ago his team was celebrating the World Series championship on the field at Fenway Park.";
    descriptLabel.numberOfLines = 0; //I would suggest something like 4 or 5 if the description string vary from 1 line to more than 5 lines.
    [descriptLabel sizeToFit];
    hFrame = descriptLabel.frame;
    if(hFrame.size.width > self.view.frame.size.width - 31.0f)
        hFrame.size.width = self.view.frame.size.width - 30.0f;
    hFrame.origin.y = timeLabel.frame.size.height + yPadding;
    descriptLabel.frame = hFrame;

    cellView.frame = CGRectMake(0.0f, 0.0f, self.view.frame.size.width, descriptLabel.frame.origin.y + descriptLabel.frame.size.height + 15.0f /*some padding*/);
    return cellView;
}

If you are using indexPath.row, you could just change the method name to be - (UIView *)getCellView:(NSIndex) *indexPath and it should work the same.

Then in your heightForRowAtIndexPath you could do

return [[self getCellView] frame].size.height;

or

return [[self getCellView:indexPath] frame].size.height

And in your cellForRowAtIndexPath you could just do the following

static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {

    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    cell.accessoryType = UITableViewCellAccessoryNone;
    cell.textLabel.text = @"";
    cell.detailTextLabel.text = @"";
}
[[cell viewWithTag:1] removeFromSuperview];
[cell addSubview:[self getCellView]; //or [cell addSubview:[self getCellView:indexPath]];
return cell;

Hope this helps. Let me know if something was unclear or not quite working. There are some stuff you may need to tweak to fit your usage, especially in cellForRowAtIndexPath, but that should be more or less everything you need to get going. Happy coding.

Chris
  • 1,004
  • 2
  • 11
  • 25
  • Thanks gonna try it out! – Realinstomp Oct 08 '14 at 01:01
  • Are you using this to work with iOS7 and iOS8? Just wondering because I'm only worried about iOS8 (even though I'm sure this works for iOS8 obviously) – Realinstomp Oct 17 '14 at 23:51
  • For iOS8 and iOS7. For the bigger phones like iPhone 6 and iPhone 6 Plus that would most likely need larger images and larger text, I added a few global variables that have the text/font and rect sizes for to fit that specific device dynamically. The code above should handle any font size since it uses sizetofit. – Chris Oct 18 '14 at 00:20