Custom Cell Row Height setting in storyboard is not responding
Asked Answered
Y

18

213

I am trying to adjust the cell height for one of the cells on my table view. I am adjusting the size from the "row height" setting inside the "size inspector" of the cell in question. When I run the app on my iPhone the cell has the default size set from the "row size" in the table view.

If I change the "row size" of the table view then the size of all cells changes. I do not want to do that as I want a custom size only for one cell. I have seen a lot of posts that have a programmatic solution to the problem, but I would prefer to do it through storyboard, if that is possible.

Yoheaveho answered 23/12, 2011 at 12:5 Comment(0)
S
295

On dynamic cells, rowHeight set on the UITableView always overrides the individual cells' rowHeight.

But on static cells, rowHeight set on individual cells can override UITableView's.

Not sure if it's a bug, Apple might be intentionally doing this?

Straub answered 27/3, 2012 at 21:49 Comment(7)
Right answer #3, and specifically because this answer applies to Interface Builder/Storyboards. If you select the cell in IB, then the Size Inspector shows Row Height at the top (with a "custom" checkbox), but if you select the whole table view the Size Inspector shows Row Height at the top there too (no "custom" in this case). As pixelfreak says, only the table view setting is used for dynamic cells. (Not sure if it's intentional)Tonsil
this answer implies that the solution would be simply to change the UITableView content from Dynamic Prototypes to Static Cells, I did that, and my whole project blew up.. almost got killed myself.Psychomotor
Is there any way to retrieve this number? Is the only way of digging deep it up is to diving into the storyboard and pulling it out from there?Guanaco
It is definitely NOT a bug, because your table is Dynamic, so how could the system know where your gone a use each of those cells ? And how many times each prototype would be used.Guardsman
There seems to also be an issue if you initially setup a table view as "static cells" and then change it to "dynamic prototypes." I had an issue where even the delegate method for rowHeight was being ignored. I first resolved it by directly editing the storyboard XML, then finally just rebuilt the storyboard scene from scratch, using dynamic prototypes from the start.Fairly
@Straub this works for me on dynamic cells. Initially I was adding a subview to the tableview and had the row height set to AutoDemension, I changed it to an arbitrary value to fit the max data returned.Jade
There is another edge to this problem, Static table view cells are only valid when embedded in a UITableViewController instance. This can then be solved with Henrik's answer.Amoroso
C
84

If you use UITableViewController, implement this method:

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

In the function of a row you can choose Height. For example,

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row == 0) {
       return 100;
    } 
    else {
       return 60;
    }
}

In this exemple, the first row height is 100 pixels, and the others are 60 pixels.

I hope this one can help you.

Comparative answered 23/12, 2011 at 15:56 Comment(2)
Beber, does one ALWAYS have to do both (check Custom and edit the Row Height value in storyboard && specify it in the heightForRowAtIndexPath)?Iaverne
Could you help me regarding this? I need to have dynamic cells with dynamic height, but if I use this method, no matter what I return all cells dissapear instead. Except on iPhone 5 the device, where it works as expected. Can you understand why?Aimo
B
34

For dynamic cells, rowHeight set on the UITableView always overrides the individual cells' rowHeight.

This behavior is, IMO, a bug. Anytime you have to manage your UI in two places it is prone to error. For example, if you change your cell size in the storyboard, you have to remember to change them in the heightForRowAtIndexPath: as well. Until Apple fixes the bug, the current best workaround is to override heightForRowAtIndexPath:, but use the actual prototype cells from the storyboard to determine the height rather than using magic numbers. Here's an example:

- (CGFloat)tableView:(UITableView *)tableView 
           heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    /* In this example, there is a different cell for
       the top, middle and bottom rows of the tableView.
       Each type of cell has a different height.
       self.model contains the data for the tableview 
    */
    static NSString *CellIdentifier;
    if (indexPath.row == 0) 
        CellIdentifier = @"CellTop";
    else if (indexPath.row + 1 == [self.model count] )
        CellIdentifier = @"CellBottom";
    else
        CellIdentifier = @"CellMiddle";

    UITableViewCell *cell = 
              [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    return cell.bounds.size.height;
}

This will ensure any changes to your prototype cell heights will automatically be picked up at runtime and you only need to manage your UI in one place: the storyboard.

Banebrudge answered 28/8, 2012 at 18:51 Comment(6)
I've posted an answer with full code including the caching; see https://mcmap.net/q/125824/-custom-cell-row-height-setting-in-storyboard-is-not-respondingBozen
Wow-wow-wow! I upvoted answer, but be AWARE! This method causes non only performance penalty as said @Brennan in comments, it also cause growing memory allocation at each reloadData something like memory leak! It's needed to use lensovet's workaround above! Spend a day to catch this memory leak!Barbbarba
Does not work in Swift, because cell.bounds.size.height always returns 0.0Unpaidfor
The cells don't exist yet at the time the system called your tableView:heightForRowAtIndexPath method. You are supposed to be able to use the indexPath that's passed to you to index into your data model and see what kind of cell will be used there, and then calculate the height for that cell and return it. That enables the system to lay out the cells in the table before it creates any cells.Unpaidfor
Also, you should not call your own cellForRowAtIndexPath method. Table views have a cellForRowAtIndexPath method that will return a cell for that index path if it is currently visible on the screen, but often that index path does not have a cell associated with it.Unpaidfor
Calling dequeueReusableCellWithIdentifier from within heightForRowAtIndexPath == infinite recursion for meChartreuse
B
22

I've built the code the various answers/comments hint at so that this works for storyboards that use prototype cells.

This code:

  • Does not require the cell height to be set anywhere other than the obvious place in the storyboard
  • Caches the height for performance reasons
  • Uses a common function to get the cell identifier for an index path to avoid duplicated logic

Thanks to Answerbot, Brennan and lensovet.

- (NSString *)cellIdentifierForIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = nil;

    switch (indexPath.section)
    {
        case 0:
            cellIdentifier = @"ArtworkCell";
            break;
         <... and so on ...>
    }

    return cellIdentifier;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = [self cellIdentifierForIndexPath:indexPath];
    static NSMutableDictionary *heightCache;
    if (!heightCache)
        heightCache = [[NSMutableDictionary alloc] init];
    NSNumber *cachedHeight = heightCache[cellIdentifier];
    if (cachedHeight)
        return cachedHeight.floatValue;

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    CGFloat height = cell.bounds.size.height;
    heightCache[cellIdentifier] = @(height);
    return height;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = [self cellIdentifierForIndexPath:indexPath];

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

    <... configure cell as usual...>
Bozen answered 2/6, 2013 at 9:6 Comment(7)
I know this is an old answer, but does anybody else find that this results in a stack overflow? When the view loads, I get UISectionRowData refreshWithSection:tableViewRowData: which calls tableView:heightForRowAtIndexPath: which calls dequeueReusableCellWithIdentifier: which calls [UISectionRowData ...] so I have a recursive stack overflow. I really wanted to use this solution, but it doesn't seem to work with iOS7.Cathexis
Does not work in Swift, because cell.bounds.size.height always returns 0.0Unpaidfor
The cells don't exist yet at the time the system called your tableView:heightForRowAtIndexPath method. You are supposed to be able to use the indexPath that's passed to you to index into your data model and see what kind of cell will be used there, and then calculate the height for that cell and return it. That enables the system to lay out the cells in the table before it creates any cells.Unpaidfor
Also, you should not call your own cellForRowAtIndexPath method. Table views have a cellForRowAtIndexPath method that will return a cell for that index path if it is currently visible on the screen, but often that index path does not have a cell associated with it.Unpaidfor
@snow-tiger I've not tried this in swift, but I don't understand your comments. I don't 'call [my] own cellForRowAtIndexPath method', and it is deliberate that I don't. I'm also aware that the cells don't exist at the time tableView:heightForRowAtIndexPath is called, that is the whole point of this code, and why it uses dequeueReusableCellWithIdentifier to get a cell. The code that is posted in this answer works. Either there is something extra going on in swift, there's something not right in the swift conversion, or are trying to solve a different problem that this question/answer targets.Bozen
Maybe it works in Objective-C but it doesn't work in Swift using iOS 8.3. Why? Simple as hell, because heightForRowAtIndexPath is called multiple times before tableView:cellForRowAtIndexPath: and dequeueReusableCellWithIdentifier returns the cell displayed on the screen, but the cell is not drawn at that moment, so that's why it returns 0.0 for the height of the cell. So this snippet doesn't work in Swift sorry. I am still obliged to use predefined magic numbers for each prototype cell.Unpaidfor
Run the following code in Swift using two dynamic (prototype) cells, in order to reproduce the beug: pastebin.com/cX7LbCruUnpaidfor
D
20

There are actually two places where you need to change to row height, first the cell (you already did change that) and now select the Table View and check the Size Inspector

Disrespectable answered 31/5, 2012 at 9:11 Comment(2)
in tableview(not cellview) > in size inspector tab > row heightGirder
Drives me nuts each time I forget this detail. No idea why adjusting the cell doesn't update the tableView automatically when using storyboards!Folsom
N
11

I think it is a bug.

Try adjust height not by Utility inspector but by mouse drag on the storyboard directly.

I solved this problem with this method.

Nutritionist answered 2/4, 2013 at 8:50 Comment(0)
G
10

If you're using swift , use like this. Don't Use storyboard to select row height. Programmatically set table row height like this,

 func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    if indexPath.row == 0 || indexPath.row == 1{
        let cell = self.tableView.dequeueReusableCellWithIdentifier("section1", forIndexPath: indexPath) as! Section1TableViewCell
        self.tableView.rowHeight = 150
        cell.label1.text = "hiiiiii"
        cell.label2.text = "Huiiilllllll"
        return cell

    } else {

        let cell = self.tableView.dequeueReusableCellWithIdentifier("section2", forIndexPath: indexPath) as! Section2TableViewCell
        self.tableView.rowHeight = 60
        cell.label3.text = "llll"
        return cell
    }

}
Gasser answered 12/10, 2015 at 5:6 Comment(1)
This works, but more generally you can do: cell.sizeToFit(); self.tableView.rowHeight = cell.frame.heightHatten
A
7

You can get height of UITableviewCell (in UITableviewController - static cells) from storyboard with help of following lines.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
   CGFloat height = [super tableView:tableView heightForRowAtIndexPath:indexPath];

    return height;
}
Alkalize answered 19/2, 2015 at 5:39 Comment(0)
P
6

Open the storyboard in the XML view and try to edit the rowHeight attribute of the wanted element.

It worked for me when I tried to set custom rowHeight for my prototyped row. It's not working via inspector, but via XML it works.

Prettify answered 4/1, 2012 at 20:0 Comment(1)
I right clicked and selected "open as" -> "source code". The rowHeight was already set to 120. I believe that storyboard has a bug there and it ignores custom cell height.Yoheaveho
F
6

You can use prototype cells with a custom height, and then invoke cellForRowAtIndexPath: and return its frame.height.:.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self tableView:tableView
                    cellForRowAtIndexPath:indexPath];
    return cell.frame.size.height;
}
Flub answered 1/2, 2013 at 7:51 Comment(2)
The addition of this method worked very well, and is now keeping all the customization in the storyboard as it should belong.Sabbatical
By far the best solutionThermography
N
4

If you want to set a static row height, you can do something like this:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 120;
}
Nela answered 12/3, 2014 at 17:57 Comment(0)
S
4

I have recently been wrestling with this. My issue was the solutions posted above using the heightForRowAtIndexPath: method would work for iOS 7.1 in the Simulator but then have completely screwed up results by simply switching to iOS 8.1.

I began reading more about self-sizing cells (introduced in iOS 8, read here). It was apparent that the use of UITableViewAutomaticDimension would help in iOS 8. I tried using that technique and deleted the use of heightForRowAtIndexPath: and voila, it was working perfect in iOS 8 now. But then iOS 7 wasn't. What was I to do? I needed heightForRowAtIndexPath: for iOS 7 and not for iOS 8.

Here is my solution (trimmed up for brevity's sake) which borrow's from the answer @JosephH posted above:

- (void)viewDidLoad {
    [super viewDidLoad];

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

    // ...
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
        return UITableViewAutomaticDimension;

    } else {
        NSString *cellIdentifier = [self reuseIdentifierForCellAtIndexPath:indexPath];
        static NSMutableDictionary *heightCache;
        if (!heightCache)
            heightCache = [[NSMutableDictionary alloc] init];
        NSNumber *cachedHeight = heightCache[cellIdentifier];
        if (cachedHeight)
            return cachedHeight.floatValue;

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    CGFloat height = cell.bounds.size.height;
    heightCache[cellIdentifier] = @(height);
    return height;
    }
}

- (NSString *)reuseIdentifierForCellAtIndexPath:(NSIndexPath *)indexPath {
    NSString * reuseIdentifier;
    switch (indexPath.row) {
        case 0:
            reuseIdentifier = EventTitleCellIdentifier;
            break;
        case 2:
            reuseIdentifier = EventDateTimeCellIdentifier;
            break;
        case 4:
            reuseIdentifier = EventContactsCellIdentifier;
            break;
        case 6:
            reuseIdentifier = EventLocationCellIdentifier;
            break;
        case 8:
            reuseIdentifier = NotesCellIdentifier;
            break;
        default:
            reuseIdentifier = SeparatorCellIdentifier;
            break;
    }

    return reuseIdentifier;
}

SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0") is actually from a set of macro definitions I am using which I found somewhere (very helpful). They are defined as:

#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
Savage answered 30/1, 2015 at 15:54 Comment(0)
R
2

The same problem occurred when working on XCode 9 using Swift 4.

Add AutoLayout for the UI elements inside the Cell and custom cell row height will work accordingly as specified.

Reichsmark answered 5/12, 2017 at 7:1 Comment(3)
Hi, please how did you do that?Chilly
You just need to add a constraint set to the bottom of the cell.Stroud
When I do this and select "automatic" in the storyboard for cell height, it wants to set all heights to 44 in the storyboard although it appears correct when I run. How can I get storyboard to display correctly?Fallacious
H
1

For dynamic cells, rowHeight set on the UITableView always overrides the individual cells' rowHeight. Just calculate the dynamic height if the content inside the row.

Hagood answered 28/6, 2016 at 6:44 Comment(0)
U
0

The only real solution I could find is this

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = ...; // Instantiate with a "common" method you'll use again in cellForRowAtIndexPath:
    return cell.frame.size.height;
}

This works and allows not to have an horrible switch/if duplicating the logic already in the StoryBoard. Not sure about performance but I guess when arriving in cellForRow: the cell being already initalized it's as fast. Of course there are probably collateral damages here, but it looks like it works fine for me here.

I also posted this here: https://devforums.apple.com/message/772464

EDIT: Ortwin Gentz reminded me that heightForRowAtIndexPath: will be called for all cells of the TableView, not only the visible ones. Sounds logical since iOS needs to know the total height to be able to show the right scrollbars. It means it's probably fine on small TableViews (like 20 Cells) but forget about it on a 1000 Cell TableView.

Also, the previous trick with XML: Same as first comment for me. The correct value was already there.

Uda answered 9/1, 2013 at 13:30 Comment(0)
F
0

Added as a comment, but posting as an answer for visibility:

There seems to also be an issue if you initially setup a table view as "static cells" and then change it to "dynamic prototypes." I had an issue where even the delegate method for heightForRowAtIndexPath was being ignored. I first resolved it by directly editing the storyboard XML, then finally just rebuilt the storyboard scene from scratch, using dynamic prototypes from the start.

Fairly answered 7/10, 2014 at 18:53 Comment(0)
U
0

Given that I did not find any solution to this problem through Interface Builder, I decided to post a programmatic solution to the problem in Swift using two dynamic cells, even though the initial question asked for a solution through Interface Builder. Regardless I think it could be helpful for the Stack Overflow community:

    import UIKit

    enum SignInUpMenuTableViewControllerCellIdentifier: String {
       case BigButtonCell = "BigButtonCell"
       case LabelCell = "LabelCell"
    }

    class SignInUpMenuTableViewController: UITableViewController {
            let heightCache = [SignInUpMenuTableViewControllerCellIdentifier.BigButtonCell : CGFloat(50),
                              SignInUpMenuTableViewControllerCellIdentifier.LabelCell : CGFloat(115)]

    private func cellIdentifierForIndexPath(indexPath: NSIndexPath) -> SignInUpMenuTableViewControllerCellIdentifier {
        if indexPath.row == 2 {
            return SignInUpMenuTableViewControllerCellIdentifier.LabelCell
        } else {
            return SignInUpMenuTableViewControllerCellIdentifier.BigButtonCell
        }
    }

   override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
       return self.heightCache[self.cellIdentifierForIndexPath(indexPath)]!
   }

   ...

  }
Unpaidfor answered 23/6, 2015 at 18:23 Comment(1)
This is all fine and good but unless I am misreading the code, it simply relies on magic numbers and completely ignores whatever value you set in IB for the dynamic cells so...not exactly a solution to OP's questionWords
C
-1

One other thing you can do is to go to your Document Outline, select the table view that your prototype cell is nested. Then on the Size Inspector, change your table view Row Height to your desired value and uncheck the Automatic box.

Corded answered 27/10, 2017 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.