0

I have grouped UITableView.

Here is my header views method - NOTE I made it so it loads only once and next time rather than reloading elements it just returns headerView if it has subviews in it already (I need that cause I have elements in headerView that change state and I do not want them to reload):

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
static NSString *headerReuseIdentifier = @"myHeader";

UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
if (headerView == nil) {
    headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:headerReuseIdentifier];//[tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
    CGRect frame=CGRectMake(0, 0, tableView.bounds.size.width, 44);
    headerView.frame=frame;
}

if (headerView.contentView.subviews.count!=0){
    return headerView;//do not reload 
}
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, tableView.frame.size.width-85, 44)];
    [label setFont:[UIFont boldSystemFontOfSize:16]];
    NSString *name=@"not found";
    [label setText:name];

    headerView.backgroundColor=[UIColor clearColor];
    [headerView.contentView addSubview:label];
    //... etc elements

return headerView;
}

I implemented similar logic for cells too, i.e. they do not reload elements if subviews already added to the cell, just to see how it works there too:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}

if (cell.contentView.subviews.count!=0) {
    return cell;//do not reload 
}

//adding elements here...
return cell;
}

=====================================================================

here how it looks the 1st time, NOTE Step:1 and Step:2 in correct ascending order enter image description here

Now, the problem, when [myTableView reloadData] is called the table looks like this:enter image description here

NOTE: the headerView views are backwards! However, cells always show up in the right order.

If [myTableView reloadData] is called again - the headerViews will show up in proper order.

I can stop this from happening by avoiding calling reloadData, but I need to reload data in the cells and not in the headerViews.

Had anybody experienced this headerViews flipping - how did you fix it?

Xcode 8.3.2, running on iPad iOS9.0 simulator

FULL Code that lets you test this wrong head views reordering:

.h file

@interface TableTestViewController : UIViewController
@property(strong,nonatomic)IBOutlet UITableView *myTable;
@property(strong,nonatomic)NSMutableArray *steps;
@end

.m file

#import "TableTestViewController.h"

@interface TableTestViewController ()

@end

@implementation TableTestViewController
@synthesize steps,myTable;

- (void)viewDidLoad {
[super viewDidLoad];
steps=[[NSMutableArray alloc]initWithObjects:@"Step 1",@"Step 2", nil];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return steps.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return 44.0;
}

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
static NSString *headerReuseIdentifier = @"myHeader";

UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
if (headerView == nil) {
    headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:headerReuseIdentifier];//[tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
    CGRect frame=CGRectMake(0, 0, tableView.bounds.size.width, 44);
    headerView.frame=frame;
}

if (headerView.contentView.subviews.count!=0) {
    return headerView;//do not reload when running
}

while (headerView.contentView.subviews.count) {
    id subview=[headerView.contentView.subviews objectAtIndex:0];
    [subview removeFromSuperview];
}

headerView.backgroundColor=[UIColor clearColor];

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, tableView.frame.size.width-85, 44)];
[label setFont:[UIFont boldSystemFontOfSize:16]];
[label setText:[steps objectAtIndex:section]];


[headerView.contentView addSubview:label];

    int btnSz=tableView.bounds.size.width/6;
    UIButton *startStepBtn = [[UIButton alloc]initWithFrame:CGRectMake(btnSz*5+5 ,5, 30, 30)];
    [startStepBtn setTitle:@"" forState:UIControlStateNormal];
    startStepBtn.tag=section+1000;
    startStepBtn.enabled=NO;
    [startStepBtn addTarget:self action:@selector(startStep:) forControlEvents:(UIControlEvents)UIControlEventTouchUpInside];

    [headerView.contentView addSubview:startStepBtn];


return headerView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
int i=1;
return 60*ceil((double)i/5)+10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}

if ([cell.contentView.subviews count]!=0) {
    return cell;//do not reload when running
}

//clear 1st
while( [cell.contentView.subviews count] ){
    id subview = [cell.contentView.subviews objectAtIndex:0];
    [subview removeFromSuperview];
}

int btnSz=tableView.bounds.size.width/6;

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, tableView.frame.size.width-85, 44)];
[label setFont:[UIFont boldSystemFontOfSize:16]];
[label setText:[steps objectAtIndex:indexPath.section]];
[cell.contentView addSubview:label];

CGRect frame;

frame = CGRectMake(btnSz*5+5 ,5, 30, 30);
    UIButton *delStepBtn = [[UIButton alloc]initWithFrame:frame];
    [delStepBtn setTitle:@"❌" forState:UIControlStateNormal];
    [delStepBtn addTarget:self action:@selector(deleteStep:) forControlEvents:(UIControlEvents)UIControlEventTouchUpInside];
    [cell.contentView addSubview:delStepBtn];

    frame = CGRectMake(btnSz*5+5 ,5+30, 30, 30);
    UIButton *editStepBtn = [[UIButton alloc]initWithFrame:frame];
    [editStepBtn setTitle:@"" forState:UIControlStateNormal];
    [editStepBtn addTarget:self action:@selector(editStep:) forControlEvents:(UIControlEvents)UIControlEventTouchUpInside];
    [cell.contentView addSubview:editStepBtn];

return cell;
}

-(void)editStep:(id)sender{
[myTable reloadData];
}

@end

implement, you will see headers in ascending order, touching the hammer button makes table to reload and headers will be now in descending order, touch again and they are back to ascending order.

However cells content always stays as it is supposed to be in ascending order.

Boris Gafurov
  • 1,427
  • 16
  • 28

2 Answers2

1

UITableViewHeaderFooterView is reusable view, so you can't be sure that UITableView will return exact same view each time.

I suggest creating a subclass of UITableViewHeaderFooterView and saving state outside in some data source.

If you don't want do this, as a workaround you can use unique reuse identifier for each section. In this case you should replace

static NSString *headerReuseIdentifier = @"myHeader";

with

NSString *headerReuseIdentifier = [NSString stringWithFormat: @"myHeader%ld", (long)section];

Sviatoslav Yakymiv
  • 7,887
  • 2
  • 23
  • 43
0

That check you're doing is not necessary:

if (headerView.contentView.subviews.count!=0){
    return headerView;//do not reload 
}

However, it's not a good practice instantiate a UILabel every time. Every instantiation should be done inside if (headerView == nil) if-case, and that UILabel should be a property of this header or cell.

Than way, you can just change the text property when you dequeued a header/cell.

bguidolim
  • 367
  • 2
  • 9