4

Sorry I'm pretty new to iOS dev.

I have a UITableView setup from cells being pulled from a single XiB nib. I've created a on/off switch in the nib, and I am trying to save the state of the switch upon viewWillDisappear for the number of cells that I have. (6 cells to be exact).

How can I loop through all the cells and save this information?

I tried this in my UIViewController to get the info for one cell:

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    UITableView *tv = (UITableView *)self.view;
    UITableViewCell *tvc = [tv cellForRowAtIndexPath:0];

}

it gives me the error "Program received signal: "EXC_BAD_INSTRUCTION".

How can I accomplish this?

Romes
  • 3,088
  • 5
  • 37
  • 52
  • why do you declare `tv`? Can't you use `self.tableView`? – Novarg Feb 13 '12 at 16:36
  • 1
    You shouldn't store the switch state in cell. Use NSArray for data model store – NeverBe Feb 13 '12 at 16:53
  • I agree with NeverBe - Don't do it the way you described (with possible exception that you have a small table, and have defined the cells as not being reusable). The recommended way to use tableviews is to maintain state in a separate *model*. *As user makes changes, you update that model with the changes*, by adding handler to each field's `editDidEnd` or similar method. Then when "Done", you are examining your custom model data - the displayed fields are not needed. – ToolmakerSteve Apr 07 '17 at 03:36

2 Answers2

11

You have to pass a valid NSIndexPath to cellForRowAtIndexPath:. You used 0, which means no indexPath.

You should use something like this:

UITableViewCell *tvc = [tv cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

BUT. Don't do this. Don't save state in the UITableViewCell.
Update your dataSource when a switch changed its state.

If you have implemented the UITableViewDataSource methods the right why your tableView reuses cells. That means the state of your cells will vanish when the cells are reused.

Your approach might work for 6 cells. But it will fail for 9 cells.
It will probably even fail if you scroll the first cell off screen.


I wrote a quick demo (if you don't use ARC add release where they are necessary) to show you how you should do it instead:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.dataSource = [NSMutableArray arrayWithCapacity:6];
    for (NSInteger i = 0; i < 6; i++) {
        [self.dataSource addObject:[NSNumber numberWithBool:YES]];
    }
}

- (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];
        UISwitch *aSwitch = [[UISwitch alloc] init];
        [aSwitch addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
        cell.accessoryView = aSwitch;
    }
    UISwitch *aSwitch = (UISwitch *)cell.accessoryView;
    aSwitch.on = [[self.dataSource objectAtIndex:indexPath.row] boolValue];
    /* configure cell */
    return cell;
}

- (IBAction)switchChanged:(UISwitch *)sender 
{
//    UITableViewCell *cell = (UITableViewCell *)[sender superview];
//    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    CGPoint senderOriginInTableView = [sender convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:senderOriginInTableView];
    [self.dataSource replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithBool:sender.on]];
}

as you see it's not very complicated to not store state in the cells :-)

Matthias Bauch
  • 89,811
  • 20
  • 225
  • 247
  • + 1 for your reply. Can your approach apply also to text fields within cells? For example, when I create a cell with its text field, I could say `[cell.textfield addTarget:self action:@selector(updateField:) forControlEvents:UIControlEventValueChanged];`. This allows to update the corresponding model every time the value changes. Does this introduce an overhead? Could be easier to register to text field delegates (`didEndEditing` for example) and update the model there? Thanks. – Lorenzo B Sep 18 '12 at 20:57
  • Thank you Matthias. I was just writing logic to save marked state into cell, and i have stopped refreshing the data source, but nervermind that, if cells got reloaded due to scrolling i would have lost that state. – Martin Berger Oct 15 '13 at 10:42
1

Moving [super viewDidDisappear:animated]; to the end of your method may be the most expedient way to address the problem. If that does not work, move the logic into viewWillDisappear:animated:.

A better way to deal with this would be to avoid reading the current state from the view at all. Rather, the view should pass the state to the model on each update. This way you would be able to harvest the current state from your model, entirely independently from the state of your view.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Are you saying to save the view rather than the different selections inside the cells? – Romes Feb 13 '12 at 16:59
  • @Romes No, I am talking about the [Model object](https://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/ModelObject.html#//apple_ref/doc/uid/TP40008195-CH31-SW1) in your MVC implementation. It *should* have up-to-the-moment state of your system, the same exact way as it is *displayed by* your view. You should be able to take this model object and save it at any time, including the time when the view is entirely gone. – Sergey Kalinichenko Feb 13 '12 at 17:33