When using \"Dynamic Prototypes\" for specifying UITableView
content on the storyboard, there is a \"Row Height\" property that can be set to Custom.
Wh
I created a category for UITableView some time ago that may come helpful for this. It stores 'prototype' cells using asociated objects for reusing the prototypes and provides a convenience method for obtaining the height of the row assigned in storyboard. The prototypes are released when the table view is deallocated.
UITableView+PrototypeCells.h
#import <UIKit/UIKit.h>
@interface UITableView (PrototypeCells)
- (CGFloat)heightForRowWithReuseIdentifier:(NSString*)reuseIdentifier;
- (UITableViewCell*)prototypeCellWithReuseIdentifier:(NSString*)reuseIdentifier;
@end
UITableView+PrototypeCells.m
#import "UITableView+PrototypeCells.h"
#import <objc/runtime.h>
static char const * const key = "prototypeCells";
@implementation UITableView (PrototypeCells)
- (void)setPrototypeCells:(NSMutableDictionary *)prototypeCells {
objc_setAssociatedObject(self, key, prototypeCells, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)prototypeCells {
return objc_getAssociatedObject(self, key);
}
- (CGFloat)heightForRowWithReuseIdentifier:(NSString*)reuseIdentifier {
return [self prototypeCellWithReuseIdentifier:reuseIdentifier].frame.size.height;
}
- (UITableViewCell*)prototypeCellWithReuseIdentifier:(NSString*)reuseIdentifier {
if (self.prototypeCells == nil) {
self.prototypeCells = [[NSMutableDictionary alloc] init];
}
UITableViewCell* cell = self.prototypeCells[reuseIdentifier];
if (cell == nil) {
cell = [self dequeueReusableCellWithIdentifier:reuseIdentifier];
self.prototypeCells[reuseIdentifier] = cell;
}
return cell;
}
@end
Usage
Obtaining the static height set in storyboard is as simple as this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [tableView heightForRowWithReuseIdentifier:@"cellIdentifier"];
}
Assuming a multi-section table view:
enum {
kFirstSection = 0,
kSecondSection
};
static NSString* const kFirstSectionRowId = @"section1Id";
static NSString* const kSecondSectionRowId = @"section2Id";
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = tableView.rowHeight; // Default UITableView row height
switch (indexPath.section) {
case kFirstSection:
height = [tableView heightForRowWithReuseIdentifier:kFirstSectionRowId];
break;
case kSecondSection:
height = [tableView heightForRowWithReuseIdentifier:kSecondSectionRowId];
}
return height;
}
And finally if the row height is dynamic:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
id thisRowData = self.allData[indexPath.row]; // Obtain the data for this row
// Obtain the prototype cell
MyTableViewCell* cell = (MyTableViewCell*)[self prototypeCellWithReuseIdentifier:@"cellIdentifier"];
// Ask the prototype cell for its own height when showing the specified data
return [cell heightForData:thisRowData];
}
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.