ChatGPT解决这个技术问题 Extra ChatGPT

Custom Cell Row Height setting in storyboard is not responding

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.


p
pixelfreak

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?


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)
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.
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?
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.
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.
P
Peter Mortensen

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.


Beber, does one ALWAYS have to do both (check Custom and edit the Row Height value in storyboard && specify it in the heightForRowAtIndexPath)?
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?
m
memmons

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.


I've posted an answer with full code including the caching; see stackoverflow.com/a/16881312/292166
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!
Does not work in Swift, because cell.bounds.size.height always returns 0.0
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.
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.
J
JosephH

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...>

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.
Does not work in Swift, because cell.bounds.size.height always returns 0.0
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.
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.
@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.
K
Kristjan H.

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


in tableview(not cellview) > in size inspector tab > row height
Drives me nuts each time I forget this detail. No idea why adjusting the cell doesn't update the tableView automatically when using storyboards!
F
Fogni

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.


C
Chathuranga Silva

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
    }

}

This works, but more generally you can do: cell.sizeToFit(); self.tableView.rowHeight = cell.frame.height
H
Hiren Panchal

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;
}

P
Peter Mortensen

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.


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.
P
P.J.Radadiya

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;
}

The addition of this method worked very well, and is now keeping all the customization in the storyboard as it should belong.
By far the best solution
P
Peter Mortensen

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

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 120;
}

s
shim

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)

M
Murari Varma

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.


Hi, please how did you do that?
You just need to add a constraint set to the bottom of the cell.
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?
A
Ash

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.


S
StuFF mc

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.


E
Eric G

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.


K
King-Wizard

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)]!
   }

   ...

  }

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 question
A
Amin Rezapour

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.