How-to update dynamicaly changing number of rows in section in UITable correclty (and its cells content)?
I'm trying to make a UITableViewController subclass, providing me me UITableView with my app settings. I have a problem when reloading data on changing states of UISwtitch .
The table has 3 sections:
section 1 - one row constantly
section 2 - UISwitch in the first row making this section consist of 1 or 3 rows (depending on the state of UISwitch)
section 3 - UISwitch in the first row making this section consist of 1 or 3 rows (depending on the state of UISwitch), but with another UISwitch and additional rows data.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch ( section )
{
case 0:
return 1;
break;
case 1:
if ( limit_passwor开发者_高级运维d_attempts )
return 3;
else
return 1;
break;
case 2:
if ( lock_when_inactive )
return 3;
else
return 1;
break;
default:
return 1;
break;
}
}
Those two UISwitch-s in the firsts rows of second and third sections are changing BOOLs respectively
BOOL limit_password_attempts;
BOOL lock_when_inactive;
My problem is when i togle the UISwitch (for exmpl) in second section, then i expect my second section extend for 2 more rows, and be filled with new data. It extends, but new cells do not look like i want to. The second row becomes a copy of first row in the next section (a row with UISwitch for third section), and the third rows returns correcly in case if the third section was not extended before, and if it was, then this row becomes a copy of the second row from third section.
So my customising of cell looks like this, i guess the error is somewhere here.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
static NSString *EnabledCellIdentifier = @"Enabled";
cell = [tableView dequeueReusableCellWithIdentifier:EnabledCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc]
initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: EnabledCellIdentifier]
autorelease];
cell.detailTextLabel.enabled = YES;
switch ( indexPath.section )
{
case 0: // section 1
cell.textLabel.text = @"Banks settings";
break;
case 1: // section 2
{
switch ( indexPath.row )
{
case 0:
{
cell.textLabel.text = @"Limit passwords attempts";
UISwitch* actSwitch = [[UISwitch alloc] initWithFrame: CGRectZero ];
[cell setEditingAccessoryView: actSwitch];
[actSwitch setTag: indexPath.section];
[actSwitch addTarget: self
action: @selector(actSwitchChanged:)
forControlEvents: UIControlEventValueChanged];
[self.view addSubview:actSwitch];
[actSwitch setOn:YES animated:NO];
[actSwitch setOn:NO animated:NO];
actSwitch.on = NO;
[cell addSubview: actSwitch];
cell.accessoryView = actSwitch;
[actSwitch release];
break;
}
case 1:
cell.textLabel.text = @"Lock after 3 atempts";
break;
case 2:
cell.textLabel.text = @"Lock for 30 min";
break;
default:
break;
}
break;
}
case 2: // section 3
{
switch ( indexPath.row )
{
case 0:
{
cell.textLabel.text = @"Lock when inactive";
UISwitch* pactSwitch = [[UISwitch alloc] initWithFrame: CGRectZero ];
[cell setEditingAccessoryView: pactSwitch];
[pactSwitch setTag: indexPath.section];
[pactSwitch addTarget: self
action: @selector(actSwitchChanged:)
forControlEvents: UIControlEventValueChanged];
[self.view addSubview:pactSwitch];
[pactSwitch setOn:YES animated:NO];
[pactSwitch setOn:NO animated:NO];
pactSwitch.on = NO;
[cell addSubview: pactSwitch];
cell.accessoryView = pactSwitch;
[pactSwitch release];
break;
}
case 1:
cell.textLabel.text = @"Lock when inactive for 20 min";
break;
case 2:
cell.textLabel.text = @"Unlock key";
break;
default:
break;
}
break;
}
default:
NSLog(@"%d", indexPath.section );
break;
}
}
return cell;
}
Looks like there is some hidden from me array with rows data, that is beeing filled onec, and table controller updates table in some way, depending on what row appeared, updating only them. Why does it makes copies, when adding row in a section. It's like an insterting a node in an array. I thought it has to push cells forward, but not just copy. Have i to delete rows somehow to make this kind of table update? I guess i'm missing some basic stuff here. Some MVC concept? But is it really necessary here to have a data model or not? Why? If yes, then how can i do it?
Thank you for any answers. Have a good day.
It was in my code. I just didn't write it down in my question. reloadData method is called after UISwtitch is toggled:
[pactSwitch setTag: indexPath.section];
[actSwitch addTarget: self
action: @selector(actSwitchChanged:)
forControlEvents: UIControlEventValueChanged];
In an actSwitchChanged method:
-(void) actSwitchChanged: (UISwitch*) pSwitch {
if ( [pSwitch tag] == 1 ) //section 2
limit_password_attempts = !limit_password_attempts ;
else if ( [pSwitch tag] == 2 ) //section 3
lock_when_inactive = !lock_when_inactive;
[self.tableView reloadData];
}
So reloadData does change the viewTable's contents, but it makes it not the way i expect it to.
Any ideas?
A data model always exists... even if it's just a bunch of if statements your UITableView calls. I think this distinction is causing the problem.
Your booleans represent your underlying data model. Like all changes to data models, you have to let the table view know when your underlying data has been changed.
Try adding the following call whenever you change one of your booleans.
[myTableViewController.tableView reloadData];
You are right, You implemented the wrong of - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
. If you looking for easy way to implementation of the cells with different layouts without subclassing of UITableViewCell
then you need, for your example:
0) read this for deep understanding of UITableView programming
1) method wich returns a type of cell for each layout
- (NSString*)typeOfCellForIndexPath:(NSIndexPath*)indexPath {
NSString *ret = @"CellWithoutSwitch";
if(indexPath.section != 0 && idextPath.row == 0) {
ret = @"CellWithSwitch";
}
return ret;
}
2) method wich returns a cell for each type
- (UITableViewCell*)cellForType:(NSString*)aType {
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: aType] autorelease];
cell.detailTextLabel.enabled = YES;
if ([aType isEqualToString:@"CellWithSwitch"]) {
// just copy of your code
// this does not need here
cell.textLabel.text = @"Limit passwords attempts";
// this needs but with CGRectMake(...) insted of CGRectZero
UISwitch* actSwitch = [[UISwitch alloc] initWithFrame: CGRectZero ];
// this does not need
[cell setEditingAccessoryView: actSwitch];
// this needs but for switch tag you should use #define SWITCH_TAG 777
[actSwitch setTag: indexPath.section];
// this does not need here
[actSwitch addTarget: self
action: @selector(actSwitchChanged:)
forControlEvents: UIControlEventValueChanged];
// wrong part of code
[self.view addSubview:actSwitch];
// this does not need here
[actSwitch setOn:YES animated:NO];
// wrong part of code
[actSwitch setOn:NO animated:NO];
actSwitch.on = NO;
// ok
[cell addSubview: actSwitch];
// wrong part of code
cell.accessoryView = actSwitch;
// ok
[actSwitch release];
}
}
3) method wich configure a cell with path
- (void)cofigureCell:(UITableViewCell*)cell forIndexPath:(NSIndexPath*)indexPath {
// just copy of your code
switch ( indexPath.section )
{
case 0: // section 1
cell.textLabel.text = @"Banks settings";
break;
case 1: // section 2
{
switch ( indexPath.row )
{
case 0:
{
cell.textLabel.text = @"Limit passwords attempts";
/* this part of code replace with UISwitch* actSwitch = (UISwitch*)[cell viewWithTag:SWITCH_TAG];
UISwitch* actSwitch = [[UISwitch alloc] initWithFrame: CGRectZero ];
[cell setEditingAccessoryView: actSwitch];
[actSwitch setTag: indexPath.section];*/
[actSwitch addTarget: self
action: @selector(actSwitchChanged:)
forControlEvents: UIControlEventValueChanged];
/* all what you need now that cofigure an state of switch with limit_password_attempts variable - [actSwitch setOn:limit_password_attempts animated:NO];
// this is junk
[self.view addSubview:actSwitch];
[actSwitch setOn:YES animated:NO];
[actSwitch setOn:NO animated:NO];
actSwitch.on = NO;
[cell addSubview: actSwitch];
cell.accessoryView = actSwitch;
[actSwitch release];*/
break;
}
case 1:
cell.textLabel.text = @"Lock after 3 atempts";
break;
case 2:
cell.textLabel.text = @"Lock for 30 min";
break;
default:
break;
}
break;
}
case 2: // section 3
{
switch ( indexPath.row )
{
case 0:
{
// look at previous configuration of switch
cell.textLabel.text = @"Lock when inactive";
UISwitch* pactSwitch = [[UISwitch alloc] initWithFrame: CGRectZero ];
[cell setEditingAccessoryView: pactSwitch];
[pactSwitch setTag: indexPath.section];
[pactSwitch addTarget: self
action: @selector(actSwitchChanged:)
forControlEvents: UIControlEventValueChanged];
[self.view addSubview:pactSwitch];
[pactSwitch setOn:YES animated:NO];
[pactSwitch setOn:NO animated:NO];
pactSwitch.on = NO;
[cell addSubview: pactSwitch];
cell.accessoryView = pactSwitch;
[pactSwitch release];
break;
}
case 1:
cell.textLabel.text = @"Lock when inactive for 20 min";
break;
case 2:
cell.textLabel.text = @"Unlock key";
break;
default:
break;
}
break;
}
default:
NSLog(@"%d", indexPath.section );
break;
}
}
4) implementation of last method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
// get type for indexPath
static NSString *EnabledCellIdentifier = [self typeOfCellForIndexPath:idextPath];
cell = [tableView dequeueReusableCellWithIdentifier:EnabledCellIdentifier];
if (cell == nil)
{
// create a cell with needed type
cell = [self cellForType:EnabledCellIdentifier];
}
// cofigure the cell
[self cofigureCell:cell forIndexPath:indexPath];
return cell;
}
Gaj, thanks for the answer.
It helped me, now table reloads correctly when i change UISwitch condition.
But i had to change a tag for uiswitch, becouse i do need to differ what kind of UISwitch i'm dealing with in the actSwitchChanged
method, to understand either it is limit_password_attempts
or lock_when_inactive
I guess it will not couse a problem in future? I mean, that SWITCH_TAG is overwritten here.
And also, why do i need to #define SWITCH_TAG 777
isn't there a better way to tag it? And why 777?
cell.textLabel.text = @"Limit passwords attempts";
UISwitch* actSwitch = (UISwitch*)[cell viewWithTag: SWITCH_TAG];
[actSwitch setTag: indexPath.section];
[actSwitch addTarget: self
action: @selector(actSwitchChanged:)
forControlEvents: UIControlEventValueChanged];
[actSwitch setOn:limit_password_attempts animated:YES];
And i have on more question: my program now works good, but, i dont understand what changed. I added to more methods: cellForType:
and cofigureCell:forIndexPath:
, but all that they do was done practicaly the same in previous version of cellForRowAtIndexPath:
.
So .. what changed here to make it work correctly? :)
精彩评论