Iphone – UITableView section header and section footer not updating (redraw problem)

iphoneuitableview

UPDATE 4.0

Seems like iOS 4.0 changed something here. Same code producing incorrect backgrounds for section header in the described scenario is working with 4.0 according to my first quick check!

Original

I have a UITableView grouped style with custom header and footer view. Inside the footer I put a UILabel and a UIButton.

Clicking on the button hides or show some rows, updates the UILabel in the footer view and finally resizes footer view.

Basically everything is working fine. BUT the text ion the label is not updated on the screen. It is updated in the UILabel text property, but only if I scroll the section footer out of the visible area and scroll it back, it is updated. So it's a typical redraw problem here of the UITableView.

I tried every method to force update like needsLayout etc. Nothing helped.

I have seen some related questions but with some different behaviour and no solution. Any help/ideas?

Thanks, Gerd

UPDATE:

My problems occurs with section footer, so here is my viewForFooterInSection.

Basically I want to collapse/expand a section, but not completely (that was an easy thing) instead only the empty cell (ItemSize empty). The footerView is large if it is collapsed and will shrink if it is expanded. Furthermore the label text will change.

- (UIView *)tableView: (UITableView *)tableView viewForFooterInSection: (NSInteger)section{
NSLog(@"viewForFooterInSection section:%i", section);

UIButton *myView;
UILabel *label;

if ([[[self.sectionStatus objectAtIndex:section] valueForKey:@"collapseStatus"] isEqual:@"collapse"]){ 
    myView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 320, 52)];
    [myView setBackgroundImage:[UIImage imageNamed:@"ItemViewFooter.png"] forState:UIControlStateNormal];
    label = [[UILabel alloc] initWithFrame:CGRectMake(20, 32, 300, 20)];
    label.text = NSLocalizedString(@"list_expand",@"");
} else { //is expanded
    myView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 320, 21)];
    [myView setBackgroundImage:[UIImage imageNamed:@"ListCollapseExpand.png"] forState:UIControlStateNormal];
    label = [[UILabel alloc] initWithFrame:CGRectMake(20, 1, 300, 20)];
    label.text = NSLocalizedString(@"list_collapse",@"");
}

myView.tag=section;
[myView addTarget:self action:@selector(collapseExpandAction:) forControlEvents:UIControlEventTouchUpInside];
myView.backgroundColor = [UIColor clearColor];
myView.adjustsImageWhenHighlighted = NO;
myView.showsTouchWhenHighlighted = YES;

label.textColor = FONTCOLOR;
label.font = [UIFont systemFontOfSize:14];
label.numberOfLines = 1;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
[myView addSubview:label];
return myView;

};

In the button action method I store status of section collapse/expand and the number of displayed rows. Than I delete or insert rows. (It has to be with insert/delete because I need the animation).

- (void) collapseExpandSection: (NSInteger) section{
NSMutableArray *paths = [NSMutableArray arrayWithCapacity:10];
NSInteger row;
NSInteger numberOfDisplayedItems=[[[self.sectionStatus objectAtIndex:section] valueForKey:@"numberOfDisplayedRows"] intValue];
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
NSInteger numberOfAllItems=[sectionInfo numberOfObjects];
Item *tmpItem=nil;
NSSet *itemsWithSizes=nil;

//filter not used cells
for ( row = 0; row < numberOfAllItems; row++ ) {
    tmpItem=[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section]];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"itemSize != nil"];
    NSSet *itemsWithSizes = [tmpItem.itemSizes filteredSetUsingPredicate:predicate];
    if ([itemsWithSizes count]==0){ 
        [paths addObject:[NSIndexPath indexPathForRow:row inSection:section]]; //all unused cells
    };
}

if (numberOfDisplayedItems == numberOfAllItems){ //currently all shown => Collapse
    [self.tableView beginUpdates];
    [[self.sectionStatus objectAtIndex:section] setValue:[NSNumber numberWithInt:(numberOfDisplayedItems-[paths count])] forKey:@"numberOfDisplayedRows"];
    [[self.sectionStatus objectAtIndex:section] setValue:@"collapse" forKey:@"collapseStatus"];
    [self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView endUpdates];
} else { //Not all shown so expand with the unused cells
    [[self.sectionStatus objectAtIndex:section] setValue:[NSNumber numberWithInt:(numberOfDisplayedItems+[paths count])] forKey:@"numberOfDisplayedRows"];
    [[self.sectionStatus objectAtIndex:section] setValue:@"expand" forKey:@"collapseStatus"];
    [self.tableView beginUpdates];
    [self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView endUpdates];
}
return;

};

Doing all this works fine in general. After the blocks begin/endupdate the viewForFooter is called for every section and the label text is set correct in the property. However the display doesn't update correctly. As soon as a redisplay is forced (srolling out- scrolling in) the display is OK.

Best Answer

There 2 problems.
First problem is that section footer not updated. Try call [tableView reloadData] or [tableView reloadSections:sections withRowAnimation:UITableViewRowAnimationFade] after your update (may be with dalay).
Second problem is memory leaks in myView and label. Also why do you use label when you can use button's internal label? P.S. Don't allocate UIButton object directly because it is factory. Call [UIButton buttonWithType:UIButtonTypeCustom] instead.

Upd: Another way to update is to update footer directly by accessing footer views.

- (void) collapseExpandSection: (NSInteger) section{

Check that section is actualy your button

 - (void) collapseExpandSection: (UIButton*) sender{
// Do update of sender here
// Do other stuff
}

Also you can try next trick: create UIView object in delegate, add your button and label on it and return instaed of buttom view itself.