How to calculate heightForRowAtIndexPath for cells

2020-04-10 01:41发布

问题:

I setup my cells like this:

So, a couple of switches, define the cells, because the data is not in the list of objects, but a set of information which should be displayed in a tableView in some different ways.

-(UITableViewCell *)value1CellForTableView:(UITableView *)tableView {
    static NSString *CellIdentifierValue1 = @"Value1Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierValue1];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifierValue1];
        cell.detailTextLabel.textAlignment = UITextAlignmentLeft;
        cell.textLabel.textAlignment = UITextAlignmentLeft;
    }
    return cell;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = nil;

    switch (indexPath.section) {
        case 0:
            //Coupon
            switch (indexPath.row) {
                case 0:
                    //Couponcode
                    cell = [self value1CellForTableView:tableView];
                    cell.textLabel.text = @"Code";
                    cell.detailTextLabel.text = presentedCoupon.couponNr;
                    break;
                case 1:
                    //Coupondescription
                    cell = [self value1CellForTableView:tableView];
                    cell.detailTextLabel.text = presentedCoupon.couponDescription;
                    cell.detailTextLabel.numberOfLines = 0;
                    cell.textLabel.text =@"Ihr Vorteil";
                    break;

            }        
            break;


        case 1:
            //Productinfo
            switch (indexPath.row) {
                case 0:
                    cell = [self defaultCellForTableView:tableView];
                    cell.imageView.image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath]  stringByAppendingPathComponent: [presentedCoupon.refProdName stringByAppendingString:@".png"]]];
                    cell.textLabel.text = presentedCoupon.refProdName;
                    break;



            }
            break;  

        case 2:
            //Shopinfo
            switch (indexPath.row) {
                case 0:
                    cell = [self defaultCellForTableView:tableView];
                    cell.textLabel.text = ((Shop*)presentedCoupon.refShop).name;
                    cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;

                    break;    
            }
            break;       
    }

    if (cell == nil) {
        cell = [self defaultCellForTableView:tableView];
        cell.textLabel.text = @"Stanni";
    }
    [cell layoutIfNeeded];
    return cell;
}

And i calculate the height like this.

 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
        NSLog(@"height");
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    CGFloat height = 24 + [cell.detailTextLabel.text sizeWithFont:cell.detailTextLabel.font constrainedToSize: CGSizeMake(cell.detailTextLabel.frame.size.width, 1000.0f) lineBreakMode:cell.detailTextLabel.lineBreakMode].height;

    return MAX(height, 44.0f);

Problem:

The problem is, as mentioned in many threads, and also visible in my log, that the height of each cell, (visible or not) is asked at the initialization of the table view. So in bigger 100+ lists, also 100+ cells are created --> wasted at startup.

Ist there another possibility to get this information when the cell is set up like this? Is it really neccessary to built the switch case sturcture again in heightForRowAtIndexPath to avoid these calls and though get the right heights for each cell?

Would it be better to hold a "datasource-list" with the single information of each cell?

But how to handle the different cell styles, custom cells.

回答1:

Two possibilities:

  1. How many cells you have? If you have a small number of cells (which seems to be the case here), you don't need cell reusing! In general, cell reusing is overused. Just create cells when you are creating your controller or when you have updated your data and put them into a NSArray.
    Then you can return them from tableView:cellForRowAtIndexPath: or measure their height.

  2. Create a separate method that returns cell text/font and use it in both delegate methods instead of reading the information from the cell directly.



回答2:

The method

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

is called for each cell before the method

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

So in the first method, cells are not yet created, and you should not try to access them.

When you create a tableView, the data source is first asked for the number of row. Then for each row, the data source is asked for the height of the row, so that the tableView know the total height of it's content, and then finally, the data source is asked for the cell (actual view) to display.

In your case, i would built the switch case structure again. Concerning the "datasource-list", I have never done it before, so maybe it's a better solution.



回答3:

If you have an array of strings in objects and are using the standard table cell then try this iOS 7 compatible magic:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    NSString* text = [self.objects objectAtIndex:indexPath.row];
    NSAttributedString * attributedString = [[NSAttributedString alloc] initWithString:text attributes:
                                             @{ NSFontAttributeName: [UIFont systemFontOfSize:18]}];

    //its not possible to get the cell label width since this method is called before cellForRow so best we can do
    //is get the table width and subtract the default extra space on either side of the label.
    CGSize constraintSize = CGSizeMake(tableView.frame.size.width - 30, MAXFLOAT);

    CGRect rect = [attributedString boundingRectWithSize:constraintSize options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];

    //Add back in the extra padding above and below label on table cell.
    rect.size.height = rect.size.height + 23;

    //if height is smaller than a normal row set it to the normal cell height, otherwise return the bigger dynamic height.
    return (rect.size.height < 44 ? 44 : rect.size.height);
}


回答4:

As I see – you have only two cell types. So you might have a @property for each type of cell (please take a look to an example below):

static NSString * const kCellIdentifier = @"kCellIdentifier";

@interface ...
@property (nonatomic, retain) UITableViewCell *cell;
@end

@implementation
@synthesize cell = cell_;

...

- (UITableViewCell *)cell {
    if (!cell_) {
        cell_ = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kCellIdentifier];
    }
    return cell_;
}

- (UITableViewCell *)cellForTableView:(UITableView *)tableView {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
    if (!cell) {
        //put it to autorelease pool to avoid EXC_BAD_ACCESS
        cell = [[self.cell retain] autorelease];
        self.cell = nil;
    }
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = self.cell;
    CGFloat height = 24 + [@"text" sizeWithFont:cell.detailTextLabel.font constrainedToSize: (CGSize){cell.detailTextLabel.frame.size.width, CGFLOAT_MAX} lineBreakMode:cell.detailTextLabel.lineBreakMode].height;
    return MAX(height, 44.0f);
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self cellForTableView:tableView];
    cell.detailTextLabel = @"text";
    return cell;
}

So the cell would be initialized only once in the beginning.