19

The code I used to create a rectangle (at least until iOS7) was

CGRect rect = [cTableView frame];
rect.origin.y += [cTableView rowHeight];
searchOverlayView = [[BecomeFirstResponderControl alloc] initWithFrame:rect];

On iOS7, cTableView (an instance of a UITableView) returned 44. Testing in iOS8 with an iPhone 5s returns -1.

Why is this happening? What is the correct code that needs to be used in order for my app to be backwards compatible with iOS7?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
cateof
  • 6,608
  • 25
  • 79
  • 153
  • I'm not entirely sure, but this could be a bug with iOS 8 (at least I hope it is). My team has had a lot of problems with UITableViews, specifically in the heightForRow method. We have submitted a bug report to apple – angerboy Aug 26 '14 at 15:16
  • Not a bug. See accepted answer this is an iOS8 change – Rog Aug 27 '14 at 12:27

6 Answers6

19

Apple changed the default row height in iOS8 to UITableViewAutomaticDimension, which is declared as -1. This means that your table view is set up for automatic cell height calculation.

You will either need to implement autoLayout (recommended) or implement the new delegate method: heightForRowAtIndexPath. Here's a great question about auto layout: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Seems like you were effectively hard coding 44 (the old default) anyway, though, so you could just do that (not recommended).

Community
  • 1
  • 1
Rog
  • 17,070
  • 9
  • 50
  • 73
  • 1
    If the value for "Row Height" is set in the xib to X, and the cells are drawing with their height as X, then isn't it a bug that the property returns -1 unless set via code? – Matt Foley Sep 30 '14 at 22:20
  • I don't think it is - it's not pretty but Apple are attempting to retain binary compatibility for older apps. Feel free to report it as one to Apple :-) – Rog Oct 01 '14 at 09:23
  • 7
    It baffles me why Apple would do this. In XIB I set rowHeight to 43 and at runtime it is 43, I set it to 45 in XIB and it is 45 at runtime. I set it to 44 in XIB and it is -1 at runtime?? Not sure how that is compatible with older apps. It broke my older app (iOS7). Not even a mention in the docs of this ridiculous behavior. As a matter of fact, here is how the Discussion in the docs begins "Row height is nonnegative..." – Swany Nov 21 '14 at 03:59
  • What is even more stupid is having a cell with, say, a label centred in it. The cell complains that it doesn't have enough constraints to determine its size. Why does IB have a `Row Height` setting for the table? Why do cells have a `Row Height` setting if we still have to go through all the estimated height palaver? What is the point of `UITableViewAutomaticDimension` if the table scrolls to the top whenever it redraws because it can't even remember the size of the cells that are displayed? – Matt Jun 18 '15 at 23:57
  • The point is that hard coded cells heights break other functionality like dynamic type. Apple simply don't want you to do this so they made the defaults do the right thing for most developers. – Rog Jun 19 '15 at 09:22
  • This just bit me, upvoting Swany because I also believe that Apple made a mistake in this case. Overriding 44 to become -1 internally is poor form. Worked around it without implementing heightForRowAtIndexPath by adding layout constraints from elements within the row, since setting the custom cell's row height to 44 also regresses to -1. – Zack Morris Jan 30 '16 at 02:43
15

This made me struggle for hours. I ended up hard coding the value to 44:

self.tableView.rowHeight = 44;
Cesare
  • 9,139
  • 16
  • 78
  • 130
Js Lim
  • 3,625
  • 6
  • 42
  • 80
  • My app still supporting iOS 6, and there are some API not available on iOS 6. hardcode is a quick & working solution – Js Lim Sep 25 '14 at 10:27
  • agreed. Hardcoding (and supporting iOS6 ;-) are not recommended. – Rog Oct 01 '14 at 09:25
  • Is there another way that works in iOS 7 upwards? This only started acting up for me in 8. – Lukas Spieß Nov 06 '14 at 00:09
  • @LukasSpieß the default value for table cell is `44`, if you're sure all rows are `44`, then set the `rowHeight` to `44` regardless the iOS version. If every rows are different height, implement this `tableView:heightForRowAtIndexPath:` method – Js Lim Nov 07 '14 at 02:51
  • @JsLim Yeah, my current way is to set the `estimatedRowHeight` property on the tableView and always implement `tableView:heightForRowAtIndexPath:`, even if I only return the same fixed value. – Lukas Spieß Nov 07 '14 at 09:23
2

There is a performance penalty for implementing heightForRowAtIndexPath that I prefer not to incur when all rows in a table are the same height and never change at runtime (it is called once for every row, each time the table is displayed).

In this situation, I continue to set "Row Height" in the XIB and use the following iOS 8 friendly code when I need rowHeight (it works on iOS 7 and below too).

NSInteger aRowHeight = self.tableView.rowHeight;
if (-1 == aRowHeight)
{
    aRowHeight = 44;
}

This allows you to keep freely editing Row Height in the XIB and will work even if Apple fixes this bug/feature in the future and a XIB set Row Height = 44 stops coming back as -1.

Swany
  • 615
  • 8
  • 14
1

If you accidentally change the row height in IB from 44 to something else (like 40), automatic cell size calculation fails. You owe me 3 hours, Apple.

Paul Bruneau
  • 1,026
  • 1
  • 9
  • 15
0

One more consideration is that if you are calculating the height based on existing view dimensions, the heightForRowAtIndexPath method may be called before viewDidLayoutSubviews.

In this case, override viewDidLayoutSubviews, and recalculate the frame.size.height value for all the visible cells.

Cesare
  • 9,139
  • 16
  • 78
  • 130
user3259235
  • 107
  • 1
  • 3
0

My solution to this problem:

@interface MCDummyTableView () <UITableViewDataSource, UITableViewDelegate>
@end

@implementation MCDummyTableView

    - (instancetype) initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
        frame = (CGRect){ 0, 0, 100, 100 };

        self = [super initWithFrame:frame style:style];
        if(!self) return self;

        self.dataSource = self;
        self.delegate = self;

        [self registerClass:[UITableViewCell class] forCellReuseIdentifier:@"CELL"];

        return self;
    }

    - (NSInteger) numberOfSections {
        return 1;
    }

    - (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return 1;
    }

    - (UITableViewCell*) cellForRowAtIndexPath:(NSIndexPath*)indexPath {
        /*
            UITableView doesn't want to generate cells until it's in the view hiearchy, this fixes that.
            However, if this breaks (or you don't like it) you can always add your UITableView to a UIWindow, then destroy it
            (that is likely the safer solution).
        */

        return [self.dataSource tableView:self cellForRowAtIndexPath:indexPath];
    }

    - (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
        return [self dequeueReusableCellWithIdentifier:@"CELL"];
    }

    - (CGFloat) defaultRowHeight {
        return [self cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].frame.size.height;
    }

@end

I really don't like hardcoding things. I use this class to cache the default cell height early on in the app.

mattsven
  • 22,305
  • 11
  • 68
  • 104