83

How can I embed a UISwitch on a UITableView cell? Examples can be seen in the settings menu.

My current solution:

UISwitch *mySwitch = [[[UISwitch alloc] init] autorelease];
cell.accessoryView = mySwitch;
testing
  • 19,681
  • 50
  • 236
  • 417

6 Answers6

194

Setting it as the accessoryView is usually the way to go. You can set it up in tableView:cellForRowAtIndexPath: You may want to use target/action to do something when the switch is flipped. Like so:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    switch( [indexPath row] ) {
        case MY_SWITCH_CELL: {
            UITableViewCell *aCell = [tableView dequeueReusableCellWithIdentifier:@"SwitchCell"];
            if( aCell == nil ) {
                aCell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"SwitchCell"] autorelease];
                aCell.textLabel.text = @"I Have A Switch";
                aCell.selectionStyle = UITableViewCellSelectionStyleNone;
                UISwitch *switchView = [[UISwitch alloc] initWithFrame:CGRectZero];
                aCell.accessoryView = switchView;
                [switchView setOn:NO animated:NO];
                [switchView addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
                [switchView release];
            }
            return aCell;
        }
        break;
    }
    return nil;
}

- (void)switchChanged:(id)sender {
    UISwitch *switchControl = sender;
    NSLog( @"The switch is %@", switchControl.on ? @"ON" : @"OFF" );
}
shim
  • 9,289
  • 12
  • 69
  • 108
zpasternack
  • 17,838
  • 2
  • 63
  • 81
  • 1
    Instead of MY_SWITCH_CELL should be the corresponding cell number I think. Nice all around solution! – testing Sep 23 '10 at 09:10
  • how do you write `aCell.accessoryView = switchView;` with bracket notation? – Jesse Jun 19 '12 at 16:03
  • 1
    @Jesse 'aCell.accessoryView = switchView;' is exactly equivalent to '[aCell setAccessoryView:switchView];'. Do you have some reason to avoid the dot-notation? – zpasternack Jun 19 '12 at 22:42
  • @Z - Thank you! I don't know why it was giving me such a hard time. I don't have any real reason other than some mild OCD. When I'm coding in objective-c I like to stick to bracket-notation as opposed to switching back and forth. I try to save the dot-notation for when I'm doing web development. – Jesse Jun 20 '12 at 10:33
  • 1
    Thanks so much for this answer! Adding the switch as a subview messes up the voice over commands. Setting it as accessory view works perfect with voiceover! – Nitin Alabur Jul 27 '12 at 19:06
  • 2
    How know the index of the switch selected? – doxsi Nov 10 '14 at 23:33
  • 2
    @doxsi `switchView.tag = indexPath.row` for detect which row switch Changed for swift – Nazmul Hasan Mar 22 '17 at 15:24
  • Won't initializing the switch in a local scope kill it and nullify the added target function call? –  Dec 25 '18 at 02:35
10

You can add a UISwitch or any other control to the cell's accessoryView. That way it will appear on the right-hand side of the cell which is probably what you want.

Echelon
  • 7,306
  • 1
  • 36
  • 34
9
if (indexPath.row == 0) {//If you want UISwitch on particular row
    UISwitch *theSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
    [cell addSubview:theSwitch];
    cell.accessoryView = theSwitch;
}
shim
  • 9,289
  • 12
  • 69
  • 108
k-thorat
  • 4,873
  • 1
  • 27
  • 36
2

You could prepare the cell in Interfacebuilder, link it to an IBOutlet of your Viewcontroller and return it when the tableview is asking for the proper row.

Instead, you could create a separate xib for the cell (again with IB) and load it using UINib upon the cells creation.

Finally, you could create the switch programmatically and add it to your cells contentview or accessoryview.

Which one suits you best largely depends on what you like to do. If your tableviews content is fixed (for a settings page etc.) the first two might work well, if the content is dynamic I'd prefer the programmatic solution. Please be more specific in what you would like to do, this would make answering your question easier.

Toastor
  • 8,980
  • 4
  • 50
  • 82
  • I'd prefer the programmatic solution (despite it's for a settings page), but I'm also interested how the first two options work. Perhaps you could explain them a little bit in more detail. – testing Sep 22 '10 at 17:11
1

for swift users

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: "TableIdentifer")
        let aswitch = UISwitch()
        cell.accessoryView = aswitch 
}
knig_T
  • 1,916
  • 16
  • 16
  • 1
    This code works noting that you cant use the variable name "switch" because that's reserved for a Switch statement. So using anything else would be fine "aSwitch" etc. – Adam Ware Jun 26 '21 at 00:30
1

This is a more complete solution where turning off and on happens on the view layer (UITableViewCell) and it forwards the events to the tableView delegate through didSelect and didDeselect:

class CustomCell: UITableViewCell {
    private lazy var switchControl: UISwitch = {
        let s = UISwitch()
        s.addTarget(self, action: #selector(switchValueDidChange(_:)), for: .valueChanged)
        return s
    }()

    override func awakeFromNib() {
        self.accessoryView = switchControl
        self.selectionStyle = .none // to show the selection style only on the UISwitch
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        (self.accessoryView as? UISwitch)?.isOn = selected
    }

    @objc private func switchValueDidChange(_ sender: UISwitch) { // needed to treat switch changes as if the cell was selected/unselected
        guard let tv = self.superview as? UITableView, let ip = tv.indexPath(for: self) else {
            fatalError("Unable to cast self.superview as UITableView or get indexPath")
        }
        setSelected(sender.isOn, animated: true)
        if sender.isOn {
            tv.delegate?.tableView?(tv, didSelectRowAt: ip)
        } else {
            tv.delegate?.tableView?(tv, didDeselectRowAt: ip)
        }
    }
}

And on your delegate


func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
    return false // to disable interaction since it happens on the switch
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // to make sure it is rendered correctly when dequeuing:
    // stuff
    if isSelected { // stored value to know if the switch is on or off
        tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
    } else {
        tableView.deselectRow(at: indexPath, animated: true)
    }
    // more stuff
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // do your thing when selecting
}

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    // do your thing when deselecting
}
rgkobashi
  • 2,551
  • 19
  • 25