Retrieve custom prototype cell height from storyboard?

拟墨画扇 提交于 2019-11-27 03:57:13

问题


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

回答1:


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.




回答2:


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


来源:https://stackoverflow.com/questions/13660004/retrieve-custom-prototype-cell-height-from-storyboard

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!