UITableView section header and section footer not updating (redraw problem)
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 e开发者_如何学运维asy 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.
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.
My problem was that I was expanding the Section header to show a search bar, but it wouldn't redraw the view until I scrolled the UITableView.
I had my own SectionHeader class that subclassed UIView and controlled the searching stuff.
After my animation, I just used this to force an update. It's not pretty but it works.
CGPoint point = CGPointMake(((UIScrollView *)self.superview).contentOffset.x,
((UIScrollView *)self.superview).contentOffset.y+1);
[((UIScrollView *)self.superview) setContentOffset:point animated:NO];
point = CGPointMake(((UIScrollView *)self.superview).contentOffset.x,
((UIScrollView *)self.superview).contentOffset.y-1);
[((UIScrollView *)self.superview) setContentOffset:point animated:NO];
Basically force the UITableView to scroll down 1 pixel and up 1 pixel.
I had an issue just like this where I wanted to update the section header after inserting a new row. I found that calling tableView reloadSections()
method with an animation setting of .None
after I call the insertRows method worked for me (both calls in the same tableView update block). I got the insert animation I wanted and also the section header was updated.
精彩评论