10

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?

Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345

3 Answers3

70

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.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];
starball
  • 20,030
  • 7
  • 43
  • 238
h4xxr
  • 11,385
  • 1
  • 39
  • 36
  • 1
    Yes, I know about the accessory view. I'd like to use a custom cell in this particular case, but thanks! – Alex Reynolds Aug 24 '09 at 15:33
  • 11
    [cell addSubview:mySwitch]; isn't necessary. cell.accessoryView retains mySwitch. – Carlton Gibson Jun 14 '10 at 10:42
  • I up-voted you, but a simple up-voted didn't seem like "thank you" enough. Thanks! Sometimes I get so focused on the problem at hand, I forgot about this little extras in the framework. – Jason Whitehorn Feb 25 '11 at 18:50
  • what approach would you take given multiple table cells like this to attach the individual switches to the right target? – Alnitak Jan 28 '16 at 14:46
5

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.

  • 1
    This did not work on my end. Thanks for the suggestion, though. – Alex Reynolds Sep 05 '09 at 10:20
  • 1
    Didn't work here either. I got: [OptionsViewController toggleImage]: unrecognized selector sent to instance 0x4b305c0'. Note that calling it without the parameter worked alright (though I obviously couldn't know which switch was calling it!) – Julian Dec 22 '10 at 01:27
  • 1
    Oops, made it work. Make sure when you add the target action to include the colon in your selector call! This works @selector(keychainOptionSwitched:), this doesn't @selector(keychainOptionSwitched). – Julian Dec 22 '10 at 01:29
0

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
  • 22,363
  • 9
  • 64
  • 71
  • self.keychainSwitch = [[(RootLabeledSwitchTableCell *)cell labeledSwitch] retain]; does not work. – Alex Reynolds Aug 24 '09 at 15:05
  • plus you probably have the property retaining anyway...what happens when you try the above line tho c rash? – Daniel Aug 24 '09 at 15:08
  • Additionally, all table view cells are in view when I call the -updateInterfaceState method, including the cell with the keychainSwitch instance. – Alex Reynolds Aug 24 '09 at 15:08
  • I do specify (nonatomic, retain) in the property definition. The app does not crash in either case: self.keychainSwitch is just nil. – Alex Reynolds Aug 24 '09 at 15:09
  • 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 Aug 24 '09 at 15:12
  • 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 Aug 24 '09 at 15:16
  • as shown here UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier]; is cell identifier constant or you keep changing it? – Daniel Aug 24 '09 at 15:18
  • CellIdentifier is defined here: NSString *CellIdentifier = [NSString stringWithFormat: @"%d:%d", [indexPath indexAtPosition:0], [indexPath indexAtPosition:1]]; – Alex Reynolds Aug 24 '09 at 15:24
  • 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 Aug 24 '09 at 15:27
  • This yields compilation warnings as this is not the correct type returned from the -dequeueReusableCellWithIdentifier: method. – Alex Reynolds Aug 24 '09 at 15:37
  • 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 Aug 24 '09 at 15:38