views:

5370

answers:

3

I have a UISwitch inside a custom UITableViewCell (the subclass of which I call RootLabeledSwitchTableCell).

The cell contains a UILabel and UISwitch next to each other.

I have a @property called keychainSwitch that points to the switch inside this custom cell:

@interface RootLabeledSwitchTableCell : UITableViewCell {
    IBOutlet UILabel *textLabel;
    IBOutlet UISwitch *labeledSwitch;
}

@property (nonatomic, retain) IBOutlet UILabel *textLabel;
@property (nonatomic, retain) IBOutlet UISwitch *labeledSwitch;

@end

In my table view delegate, I have added a selector that is called if the switch state is flipped:

- (UITableViewCell *) tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
   NSString *CellIdentifier = [NSString stringWithFormat: @"%d:%d", [indexPath indexAtPosition:0], [indexPath indexAtPosition:1]];
   UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];
   if (cell == nil) {
       switch (indexPath.section) {
       case(kMyFirstSection): {
           switch (indexPath.row) {
               case(kMyFirstSectionFirstRow) {
                   [cellOwner loadMyNibFile:@"RootLabeledSwitchTableCell"];
                   cell = (RootLabeledSwitchTableCell *)cellOwner.cell;
                   self.keychainSwitch = [(RootLabeledSwitchTableCell *)cell labeledSwitch];
                   [self.keychainSwitch addTarget:self action:@selector(keychainOptionSwitched) forControlEvents:UIControlEventValueChanged];
                   break;
               }
               // ...
           }
       }
       // ...
   }
}

So this selector works correctly:

- (void) keychainOptionSwitched {
   NSLog(@"Switched keychain option from %d to %d", ![self.keychainSwitch isOn], [self.keychainSwitch isOn]);
}

However, I cannot use the UISwitch instance's -setOn:animated: method to initialize its initial state:

- (void) updateInterfaceState {
   BOOL isFirstTimeRun = [[[NSUserDefaults standardUserDefaults] objectForKey:kIsFirstTimeRunKey] boolValue];
   if (isFirstTimeRun) {
      [self.keychainSwitch setOn:NO animated:NO];
   }
}

From testing, self.keychainSwitch is nil which is causing -setOn:animated to do nothing, but I can still operate the switch and the NSLog statement will correctly print the switch state changes, e.g.:

[Session started at 2009-08-24 07:04:56 -0700.]
2009-08-24 07:04:58.489 MyApp[29868:20b] keychain switch is: nil
2009-08-24 07:05:00.641 MyApp[29868:20b] Switched keychain option from 1 to 0
2009-08-24 07:05:01.536 MyApp[29868:20b] Switched keychain option from 0 to 1
2009-08-24 07:05:02.928 MyApp[29868:20b] Switched keychain option from 1 to 0

Is there something I am missing about setting self.keychainSwitch in the UITableView delegate method?

A: 

I think whats happening here is that you are trying to invoke the operation when the cell is out of view, so whats going on is that the cell is unloaded at that time therefore you are getting a nil for the keychain switch, once it comes into view then its no longer nil since its reloaded. But i see that you are assigning the switch like so

 self.keychainSwitch = [(RootLabeledSwitchTableCell *)cell labeledSwitch];

Its a reference to the switch in the table cell and you are not retaining it, so it makes since that the switch is becoming nil since , when the cell goes out of view is unloaded and keychainSwitch is becoming nil as a result and therefore your class member is becoming nil as well. You might want to retain keychainSwitch and then when reconstructing your table c ell get the switch out of keychainSwitch, or something of the sort. Hope this helps

Daniel
self.keychainSwitch = [[(RootLabeledSwitchTableCell *)cell labeledSwitch] retain]; does not work.
Alex Reynolds
plus you probably have the property retaining anyway...what happens when you try the above line tho c rash?
Daniel
Additionally, all table view cells are in view when I call the -updateInterfaceState method, including the cell with the keychainSwitch instance.
Alex Reynolds
I do specify (nonatomic, retain) in the property definition. The app does not crash in either case: self.keychainSwitch is just nil.
Alex Reynolds
the problem i think is still the dequeing of cells, it kind of hard to figure out a good solution without looking at more code, but maybe try keeping your switches ( or switch values) in an array in your viewController and when you ask for a cell to reload you can get its value from there (or set its switch view). Since you are using only one dequeue identifier your cells wont retain the state of the switch so you will have to reset it everytime you reload cells anyway (if this is the behavior you want)
Daniel
I guess I'm not sure I understand your answer, as I am using a different dequeue identifier for each section and row-within-section.
Alex Reynolds
as shown here UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier]; is cell identifier constant or you keep changing it?
Daniel
CellIdentifier is defined here: NSString *CellIdentifier = [NSString stringWithFormat: @"%d:%d", [indexPath indexAtPosition:0], [indexPath indexAtPosition:1]];
Alex Reynolds
ok they are unique, now let me suggest you do this (given you know the indexpath of the row you are trying to set the switch off), try RootLabeledSwitchTableCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier]; and then set the switch off directly on the cell...
Daniel
This yields compilation warnings as this is not the correct type returned from the -dequeueReusableCellWithIdentifier: method.
Alex Reynolds
cast it (RootLabeledSwitchTableCell*), make sure when you do this that the cell you access will be of this type (you can alwyas ask the type o f it like ) [cell isKindOfClass:[RootLabeleCell class]
Daniel
+23  A: 

I spent ages fiddling around with custom UITableViewCells with a simple label and switch on it before I discovered I could just add a UISwitch as an accessory view. You're probably aware of this anyway, and want the custom cell for other reasons, but just in case I wanted to mention this!

You do this as follows (in the -tableView:cellForRowAtIndexPath method):

UISwitch *mySwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
[cell addSubview:mySwitch];
cell.accessoryView = mySwitch;

The accessoryView bit also takes care of the sizing and positioning so we can get away with CGRectZero.

You're then able to use the system control event and setOn method as follows (note we cast it as the UISwitch pointer that we know it is):

[(UISwitch *)cell.accessoryView setOn:YES];   // Or NO, obviously!
[(UISwitch *)cell.accessoryView addTarget:self action:@selector(mySelector)
     forControlEvents:UIControlEventValueChanged];

Hope that helps!

h4xxr
Yes, I know about the accessory view. I'd like to use a custom cell in this particular case, but thanks!
Alex Reynolds
[cell addSubview:mySwitch]; isn't necessary. cell.accessoryView retains mySwitch.
Carlton Gibson
+1  A: 

I did this a while ago. You should change your selctor

[self.keychainSwitch addTarget:self action:@selector(keychainOptionSwitched:) forControlEvents:UIControlEventValueChanged];

And then update your method to

-(void) keychainOptionSwitched:(id)sender {
UISwitch *tempSwitch = (UISwitch *)sender;
}

Now tempSwitch is the switch that was changed.

This did not work on my end. Thanks for the suggestion, though.
Alex Reynolds