Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cell shows on top of section header

I have an UITableView with custom cells and custom headers. When I move one cell upon editing, it pops up on to of the header view. How can I keep the header view on top of all the cells?

The app uses storyboard, in case that makes a difference.

This is how it looks? https://www.dropbox.com/s/wg8oiar0d9oytux/iOS%20SimulatorScreenSnapz003.mov

This is my code:

[...]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ListCell";
    ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    int handleSection = [self sectionToHandle:indexPath.section];

    switch (handleSection)
    {
        case PrivateLists:
        {
            if (tableView.isEditing && (indexPath.row == self.privateLists.count))
            {
                cell.textField.text = NSLocalizedString(@"Lägg till ny lista", nil);
                cell.textField.enabled = NO;
                cell.textField.userInteractionEnabled = NO;
                cell.accessoryType = UITableViewCellAccessoryNone;
                cell.editingAccessoryView.hidden = YES;
            }
            else
            {
                List *list = [self.privateLists objectAtIndex:indexPath.row];
                cell.textField.text = list.name;
                cell.textField.enabled = YES;
                cell.textField.userInteractionEnabled =YES;
                cell.backgroundColor = [UIColor clearColor];

                cell.onTextEntered = ^(NSString* enteredString){
                    list.name = enteredString;
                    UpdateListService *service = [[UpdateListService alloc]initServiceWithList:list];
                    [service updatelistOnCompletion:
                     ^(BOOL success){
                         DLog(@"Updated list");

                         NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
                         [self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];

                         [self moveListToTop:list.ListId newIndexPath:newPath];
                         justMovedWithoutSectionUpdate = YES;
                     }
                                                    onError:
                     ^(NSError *error){
                         [[ActivityIndicator sharedInstance] hide];
                         [[ErrorHandler sharedInstance]handleError:error fromSender:self];
                     }];
                };
            }
        }
            break;
        default:
            return 0;
            break;
    }

    return cell;
}

-(UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section
{
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 22)];

    UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 21)];
    [textLabel setFont:[[AXThemeManager sharedTheme]headerFontWithSize:15.0]];
    [textLabel setTextColor:[[AXThemeManager sharedTheme]highlightColor]];
    [textLabel setText:@"SECTION TITLE"];
    [textLabel setBackgroundColor:[UIColor clearColor]];

    UIImageView *backgroundView = [[UIImageView alloc]initWithImage:[AXThemeManager sharedTheme].tableviewSectionHeaderBackgroundImage];
    [backgroundView setFrame:view.frame];
    [view addSubview:backgroundView];
    [view sendSubviewToBack:backgroundView];
    [view addSubview:textLabel];

    return view;
}

- (float)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 22;
}

- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 44;
}
[...]
like image 379
Paul Peelen Avatar asked Dec 18 '12 06:12

Paul Peelen


3 Answers

Good news! I was able to fix/workaround your problem in two different ways (see below).

I would say this is certainly an OS bug. What you are doing causes the cell you have moved (using moveRowAtIndexPath:) to be placed above (in front of) the header cell in the z-order.

I was able to repro the problem in OS 5 and 6, with cells that did and didn't have UITextFields, and with the tableView in and out of edit mode (in your video it is in edit mode, I noticed). It also happens even if you are using standard section headers.

Paul, you say in one of your comments:

I solved it badly using a loader and "locking" the table while preforming a reloadData

I am not sure what you mean by "using a loader and locking the table", but I did determine that calling reloadData after moveRowAtIndexPath: does fix the problem. Is that not something you want to do?

[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
//[self.tableView reloadData];
// per reply by Umka, below, reloadSections works and is lighter than reloadData:
[self reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationNone];

If you dont want to do that, here is another solution that feels a little hacky to me, but seems to work well (iOS 5+):

__weak UITableViewCell* blockCell = cell;  // so we can refer to cell in the block below without a retain loop warning.
...
cell.onTextEntered = ^(NSString* sText)
{
    // move item in my model
    NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
    [self.itemNames removeObjectAtIndex:indexPath.row];
    [self.itemNames insertObject:sText atIndex:0];

    // Then you can move cell to back
    [self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
    [self.tableView sendSubviewToBack:blockCell];  // a little hacky

    // OR per @Lombax, move header to front
    UIView *sectionView = [self.tableView headerViewForSection:indexPath.section];
    [self.tableView bringSubviewToFront:sectionView];
like image 165
Morgan Avatar answered Nov 15 '22 11:11

Morgan


It's a bug. You can quickly solve it by adding, after the line:

[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];

this lines:

UIView *sectionView = [self.tableView headerViewForSection:indexPath.section];
[self.tableView bringSubviewToFront:sectionView];
like image 36
LombaX Avatar answered Nov 15 '22 10:11

LombaX


Not a solution but your code has number of issues. Who knows what happens if you fix them ;)

(1) Your cell may be nil after this line:

ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

It should look like this:

ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
                cell = [[[ListCell alloc] initWithStyle:style
                                                    reuseIdentifier:cellIdentifier] autorelease];
}

(2) Two memory leaks in -(UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section

->>Fix (When you add the label as subview it gets +1 ref).

UILabel *textLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 21)] autorelease];

->>Fix (When you add the view as subview it gets +1 ref).

UIImageView *backgroundView = [[[UIImageView alloc]initWithImage:[AXThemeManager sharedTheme].tableviewSectionHeaderBackgroundImage] autorelease];

(3) Not a defect but this may help you. Try using this instead of [table reloadData]. It allows to animate things nicely and is not such a hardcore way to update the table. I'm sure it is much more lightweight. Alternatively try to look for other "update" methods. Given you don't delete rows in your example, something like [updateRowsFrom:idxFrom to:idxTo] would help.

[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0]
                              withRowAnimation:UITableViewRowAnimationAutomatic];
like image 3
Cynichniy Bandera Avatar answered Nov 15 '22 12:11

Cynichniy Bandera