Dynamic height of UITableViewCell with variable number of subzones

I have a UITableViewCell that is dynamically populated with data received from a remote source. It has a variable number of subzones based on the type of element represented in the cell. I dynamically calculate the size of the cell contents using the NSMutableAttributedString and boundingRectWithSize: methods. I accept the return values ​​and computes the combinedHeight variable, which is stored in NSMutableDictionary and is associated with the indexPath cell. Everything works fine, but when I try to use the values ​​from the dictionary in the heightForRowAtIndexPath: method, it uses only the first set of visible cells, and after that it is still 0.

My question is how can I dynamically change a cell to contain all subzones. I also add a custom view to the contents of the cellView, which is less than the width of the cell, so that I can achieve the unique style that we are going to make. This is the style we are going for.

Here is my code:

  - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (areFeedItemsLoaded && feedItems.count > 0 && indexPath.row < feedItems.count) { CGFloat height = [[_cellHeights objectForKey:[self keyForIndexPath:indexPath]] floatValue]; return MAX(90, height); } return 44; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell; if (areFeedItemsLoaded) { if (feedItems.count == 0) { if (indexPath.row == 0) { cell = [EmptyTableViewCell getBlankCell:tableView]; } else { EmptyTableViewCell *eCell = [EmptyTableViewCell getEmptyCell:tableView withHeaderText:@"No Recent Activity"]; [eCell setLinkText:@"Your team activity on Hudl will appear here." withUrl:nil]; cell = eCell; } } else if (indexPath.row < feedItems.count) { FeedItem *item = [feedItems objectAtIndex:indexPath.row]; cell = [self.tableView dequeueReusableCellWithIdentifier:FeedItemCellIdentifier forIndexPath:indexPath]; if (cell == nil) { cell = [[FeedItemTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:FeedItemCellIdentifier]; } cell.clipsToBounds = YES; cell.backgroundColor = [UIColor clearColor]; CGFloat combinedHeight = 8; UIView *customContentView = [cell viewWithTag:customContentTagId]; if (customContentView == nil) { customContentView = [[UIView alloc] init]; customContentView.tag = customContentTagId; customContentView.backgroundColor = [UIColor colorWithR:100 g:100 b:100 a:1]; [cell.contentView addSubview:customContentView]; } customContentView.frame = CGRectMake(50, 0, 270, 70); UIImageView *userImage = (UIImageView *)[cell viewWithTag:userImageTagId]; if (userImage == nil) { userImage = [[UIImageView alloc] init]; userImage.tag = userImageTagId; [customContentView addSubview:userImage]; } userImage.frame = CGRectMake(8, 8, 45, 45); userImage.layer.cornerRadius = CGRectGetWidth(userImage.frame) / 2; userImage.layer.masksToBounds = YES; NSURL *imageUrl = item.imageSourceUrl; [userImage setImageWithURL:imageUrl placeholderImage:[UIImage imageNamed:@"default-user.png"]]; CGFloat minX = CGRectGetMaxX(userImage.frame) + 8; UILabel *titleLabel = (UILabel *)[cell viewWithTag:titleTagId]; if (titleLabel == nil) { titleLabel = [[UILabel alloc] init]; titleLabel.numberOfLines = 0; titleLabel.textColor = [UIColor colorWithR:204 g:204 b:204 a:1]; titleLabel.font = [UIFont fontWithName:@"Helvetica" size:14]; titleLabel.lineBreakMode = NSLineBreakByWordWrapping; titleLabel.tag = titleTagId; [customContentView addSubview:titleLabel]; } NSString *title = [NSString stringWithFormat:@"%@ %@", item.creatorName, item.title]; NSMutableAttributedString *titleText = [[NSMutableAttributedString alloc] initWithString:title]; [titleText addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithR:204 g:204 b:204 a:1] range:NSMakeRange(0, title.length - 1)]; [titleText addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"Helvetica-Bold" size:14] range:NSMakeRange(0, item.creatorName.length)]; [titleText addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"Helvetica" size:15] range:NSMakeRange(item.creatorName.length + 1, title.length - item.creatorName.length - 1)]; titleLabel.attributedText = titleText; CGFloat width = CGRectGetWidth(customContentView.frame) - (CGRectGetMaxX(userImage.frame) + 8) - 8; width = MIN(width, 200); CGSize titleSize = [titleText boundingRectWithSize:CGSizeMake(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size; titleLabel.frame = CGRectMake(minX, CGRectGetMinY(userImage.frame), ceil(titleSize.width), ceil(titleSize.height)); combinedHeight += ceil(titleSize.height) + 8; UIView *thumbnailsView = [cell viewWithTag:thumbnailTagId]; UILabel *descriptionLabel = (UILabel *)[cell viewWithTag:descriptionTagId]; if ([item.feedItemType intValue] == PlaylistFI || [item.feedItemType intValue] == PlaylistAndNotesFI) { if ([item.uris count] > 0) { if (thumbnailsView == nil) { thumbnailsView = [[UIView alloc] init]; thumbnailsView.tag = thumbnailTagId; [customContentView addSubview:thumbnailsView]; } else { for (UIView *view in thumbnailsView.subviews) { [view removeFromSuperview]; } } thumbnailsView.frame = CGRectMake(minX, CGRectGetMaxY(titleLabel.frame) + 8, width, 40); combinedHeight += 48; for (int i = 0; i < 3 && i < item.uris.count; i++) { NSURL *url = [item.uris objectAtIndex:i]; UIImageView *imageView = [[UIImageView alloc] init]; int x = i * 60 + i * 8; imageView.frame = CGRectMake(x, 0, 60, 40); [imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:@"soft-dark-grad.png"]]; [thumbnailsView addSubview:imageView]; } } if (descriptionLabel == nil) { descriptionLabel = [[UILabel alloc] init]; descriptionLabel.tag = descriptionTagId; descriptionLabel.font = [UIFont systemFontOfSize:14]; descriptionLabel.textColor = [UIColor colorWithR:204 g:204 b:204 a:1]; descriptionLabel.numberOfLines = 0; descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping; [customContentView addSubview:descriptionLabel]; } NSMutableAttributedString *descriptionText = [[NSMutableAttributedString alloc] initWithString:item.descriptionText]; [descriptionText addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"Helvetica" size:14] range:NSMakeRange(0, item.descriptionText.length)]; descriptionLabel.attributedText = descriptionText; CGSize descriptionSize = [descriptionText boundingRectWithSize:CGSizeMake(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size; descriptionLabel.frame = CGRectMake(minX, CGRectGetMaxY(thumbnailsView.frame) + 8, ceil(descriptionSize.width), ceil(descriptionSize.height)); combinedHeight += ceil(descriptionSize.height) + 8; } else { if (thumbnailsView != nil) { [thumbnailsView removeFromSuperview]; } if (descriptionLabel != nil) { [descriptionLabel removeFromSuperview]; } } UILabel *messageLabel = (UILabel *)[cell viewWithTag:messageTagId]; if ([item.feedItemType intValue] == MessageFI) { if (messageLabel == nil) { messageLabel = [[UILabel alloc] init]; messageLabel.tag = messageTagId; messageLabel.font = [UIFont systemFontOfSize:14]; messageLabel.textColor = [UIColor colorWithR:204 g:204 b:204 a:1]; messageLabel.numberOfLines = 0; messageLabel.lineBreakMode = NSLineBreakByWordWrapping; [customContentView addSubview:messageLabel]; } NSMutableAttributedString *messageText = [[NSMutableAttributedString alloc] initWithString:item.message]; [messageText addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"Helvetica" size:14] range:NSMakeRange(0, item.message.length)]; CGSize messageSize = [messageText boundingRectWithSize:CGSizeMake(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size; messageLabel.frame = CGRectMake(minX, CGRectGetMaxY(titleLabel.frame) + 8, ceil(messageSize.width), ceil(messageSize.height)); messageLabel.attributedText = messageText; combinedHeight += ceil(messageSize.height) + 8; } else { if (messageLabel != nil) { [messageLabel removeFromSuperview]; } } if ([item.feedItemType intValue] == PlaylistFI || [item.feedItemType intValue] == PlaylistAndNotesFI || [item.feedItemType intValue] == ArticleFI) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.selectionStyle = UITableViewCellSelectionStyleBlue; } else { cell.accessoryType = UITableViewCellAccessoryNone; cell.selectionStyle = UITableViewCellSelectionStyleNone; } [_cellHeights setObject:@(combinedHeight) forKey:[self keyForIndexPath:indexPath]]; CGRect frame = customContentView.frame; frame.size.height = MAX(70, combinedHeight); customContentView.frame = frame; } else if (!requestFailed) { cell = [LoadingTableViewCell getLoadingCell:tableView]; } else { cell = [ErrorTableViewCell getErrorCell:tableView]; } } else { cell = (indexPath.row == 0 || indexPath.row < feedItems.count) ? [EmptyTableViewCell getBlankCell:tableView] : [LoadingTableViewCell getLoadingCell:tableView]; } return cell; } - (NSIndexPath *)keyForIndexPath:(NSIndexPath *)indexPath { if ([indexPath class] == [NSIndexPath class]) { return indexPath; } return [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section]; } 
+2
source share
1 answer

The problem with your code is the order in which the height is calculated. If you set a breakpoint in both methods, you will see that tableView:heightForRowAtIndexPath: is actually called BEFORE tableView:cellForRowAtIndexPath: This means that performing height calculations when setting up the cell will not work (since the height will return 0 before it is set).

Here are my suggestions:

Create a subclass of UITableViewCell and move all the initialization and configuration logic that is built into tableView:cellForRowAtIndexPath: into this subclass.

Once a cell is configured with any data properties that can change its height, set the frame height inside the cell because you will use it in the next step.

Then, to calculate the height yourself, use the method in the cell itself, which looks something like this:

 + (CGFloat)heightForCellWithWithData:// use whatever parameters are usually set on a cell that might affect it height { static dispatch_once_t once; static CustomCellClass *sizingCell; // use your custom cell class here dispatch_once(&once, ^{ sizingCell = [[self alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"sizingCell"]; }); // set cell data properties here return sizingCell.frame.size.height; } 

Then, to get the height, you simply do:

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (areFeedItemsLoaded && feedItems.count > 0 && indexPath.row < feedItems.count) { return [CustomCellClass heightForCellWithWithData:whateverData]; } return 44; } 
+3
source

All Articles