iOS - heightForRowAtIndexPath does not fire during scrolling
I am writing a simple iOS app using Xcode4, which uses a table view to display a list of stories (fetched from a URL). I'm displaying the story titles as UILabels, and as a subview of the table cell.
I am over-riding heightForRowAtIndexPath
to calculate the correct height for the cells according to the length of each story title. I'm adding the label to the cell in cellForRowAtIndexPath
. When I run the app in the simulator, everything is rendered well. However: when I scroll down and scroll up, the labels get messed up. They get truncated and over-run. I debugged a little, and found that the heightForRowAtIndexPath
method is not fired during scrolling, so the cell heights are not re-calculated, and therefore the label text overflows, and gets rendered ugly. Here is the relevant code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[cell autorelease];
}
/* NOTE: code to load trimmedTitle dynamically is snipped */
NSString* trimmedTitle;
UIFont *cellFont = [UIFont fontWithName:@"Georgia" size:14.0];
CGSize constraintSize = CGSizeMake(280.0f, MAXFLOAT);
CGSize labelSize = [trimmedTitle sizeWithFont:cellFont constrainedToSize:constraintSize
lineBreakMode:UILineBreakModeWordWrap];
UILabel* tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(60, 0, 230, labelSize.height)];
tempLabel.lineBreakMode = UILineBreakModeWordWrap;
tempLabel.text = trimmedTitle;
tempLabel.numberOfLines = 0;
[cell.contentView addSubview:tempLabel];
[tempLabel release];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString* cellText; // code to load cellText dynamically is snipped off
UIFont *cellFont = [UIFont fontWithName:@"Georgia" size:14.0];
CGSize constraintSize = CGSizeMake(230.0f, MAXFLOAT);
CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordW开发者_如何学JAVArap];
return labelSize.height + 20;
}
In situations like these I typically pre-calculate the heights of all of my rows, store them in an array, and then just returns those heights in heightForRowAtIndexPath
. This way the tableview knows the height of each cell and cells be conform to that height even after reuse. I don't know of a way to force a calculation of the cell height beyond looking for when a cell will be viewable and reloading it, which seems too costly.
Update: some example code:
I have a method called - (void)calculateHeights
which does the same calculation you had in heightForRowAtIndexPath
, but stores the result in my mutable array heights_
ivar:
- (void)calculateHeights {
[heights_ removeAllObjects]
for (Widget *myWidget in modelWidgetArray) {
NSString* cellText; // code to load cellText dynamically is snipped off
UIFont *cellFont = [UIFont fontWithName:@"Georgia" size:14.0];
CGSize constraintSize = CGSizeMake(230.0f, MAXFLOAT);
CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
[heights_ addObject:[NSNumber numberWithFloat:labelSize.height + 20.0f]];
}
}
And then in heightForRowAtIndexPath
, given a 1-section table view:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [heights_ objectAtIndex:[indexPath row]];
}
If your table view has more than one section you'll need to do some math to convert to the one-dimensional heights_
array and back again. Also, any time you -reloadData
you'll need to -calculateHeights
as well.
The -tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
method is invoked before the scroll view is composed.
The purpose of calling this method before calling -tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
and not fired during the scrolling is that the table view (Which is inherited from UIScrollView) need to know the whole height of the contentView. Once the table view knows all the height, it cached the height before you call -reloadData
Your problem means you need to clear the content in the cell's -prepareForReuse
and call -setNeedLayout
to layout all the new contents.
you can use tag for your label to avoid messed up to each other
UILabel *label= (UILabel*)[cell viewWithTag:2];
label.lineBreakMode = UILineBreakModeWordWrap;
label.numberOfLines = 0;
精彩评论