When using "Dynamic Prototypes" for specifying UITableView
content on the storyboard, there is a "Row Height" property that can be set to Custom.
When instantiating cells, this custom row height is not taken into account. This makes sense, since which prototype cell I use is decided by my application code at the time when the cell is to be instantiated. To instantiate all cells when calculating layout would introduce a performance penalty, so I understand why that cannot be done.
The question then, can I somehow retrieve the height given a cell reuse identifier, e.g.
[myTableView heightForCellWithReuseIdentifier:@"MyCellPrototype"];
or something along that line? Or do I have to duplicate the explicit row heights in my application code, with the maintenance burden that follows?
Solved, with the help of @TimothyMoose:
The heights are stored in the cells themselves, which means the only way of getting the heights is to instantiate the prototypes. One way of doing this is to pre-dequeue the cells outside of the normal cell callback method. Here is my small POC, which works:
#import "ViewController.h" @interface ViewController () { NSDictionary* heights; } @end @implementation ViewController - (NSString*) _reusableIdentifierForIndexPath:(NSIndexPath *)indexPath { return [NSString stringWithFormat:@"C%d", indexPath.row]; } - (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if(!heights) { NSMutableDictionary* hts = [NSMutableDictionary dictionary]; for(NSString* reusableIdentifier in [NSArray arrayWithObjects:@"C0", @"C1", @"C2", nil]) { CGFloat height = [[tableView dequeueReusableCellWithIdentifier:reusableIdentifier] bounds].size.height; hts[reusableIdentifier] = [NSNumber numberWithFloat:height]; } heights = [hts copy]; } NSString* prototype = [self _reusableIdentifierForIndexPath:indexPath]; return [heights[prototype] floatValue]; } - (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 3; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString* prototype = [self _reusableIdentifierForIndexPath:indexPath]; UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:prototype]; return cell; } @end
For static (non-data-driven) height, you can just dequeue the cell once and store the height:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { static NSNumber *height; if (!height) { UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCustomCell"]; height = @(cell.bounds.size.height); } return [height floatValue]; }
For dynamic (data-driven) height, you can store a prototype cell in the view controller and add a method to the cell's class that calculates the height, taking into account the default content of the prototype instance, such as subview placement, fonts, etc.:
- (MyCustomCell *)prototypeCell { if (!_prototypeCell) { _prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCustomCell"]; } return _prototypeCell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // Data for the cell, e.g. text for label id myData = [self myDataForIndexPath:indexPath]; // Prototype knows how to calculate its height for the given data return [self.prototypeCell myHeightForData:myData]; }
Of course, if you're using custom height, you probably have multiple cell prototypes, so you'd store them in a dictionary or something.
As far as I can tell, the table view doesn't attempt to reuse the prototype, presumably because it was dequeued outside of cellForRowAtIndexPath:
. This approach has worked very well for us because it allows the designer to modify cells layouts in the storyboard without requiring any code changes.
Edit: clarified the meaning of sample code and added an example for the case of static height.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With