altralaser
altralaser

Reputation: 2073

UILabel and UISwitch in a table view cell

I try to build a kind of checkbox component for my Swift app. I'm using a UISwitch in combination with a UILabel and I got the suggestion to wrap it in a table view to get the same behavior like in Apple's settings app.
What I already have is the following code:

class ViewController: UITableViewController {
    var myFirstLabel : UILabel?
    var myFirstSwitch : UISwitch?
    var mySecondLabel : UILabel?
    var mySecondSwitch : UISwitch?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "MyCellID", for: indexPath)

        switch indexPath.row {
        case 0:
            myFirstLabel = UILabel(frame: CGRect(x: 5, y: 10, width: 100, height: 21))
            myFirstLabel?.text = "First label"
            myFirstSwitch = UISwitch(frame: CGRect(x: 105, y: 5, width: 51, height: 31))
            myFirstSwitch?.addTarget(self, action: #selector(stateChanged), for: .touchUpInside)
            cell.contentView.addSubview(myFirstLabel!)
            cell.contentView.addSubview(myFirstSwitch!)
            break
        case 1:
            mySecondLabel = UILabel(frame: CGRect(x: 5, y: 10, width: 160, height: 21))
            mySecondLabel?.text = "Second label"
            mySecondSwitch = UISwitch(frame: CGRect(x: 165, y: 5, width: 51, height: 31))
            mySecondSwitch?.addTarget(self, action: #selector(stateChanged), for: .touchUpInside)
            cell.contentView.addSubview(mySecondLabel!)
            cell.contentView.addSubview(mySecondSwitch!)
            break
        default:
            break
        }

        return cell
    }

    func stateChanged(sender : UISwitch) {
        if sender == myFirstSwitch! {
        } else if sender == mySecondSwitch! {
        }
    }
}

My problem is that now I see the label texts twice in the cell and I don't know why. And I also don't know if the design is the right one.

Upvotes: 1

Views: 1171

Answers (2)

DanielH
DanielH

Reputation: 685

The reason you see multiple labels inside a single cell is that when the cell is dequeued it already has the label from the previous presentation. UITableView reuses existing cell objects so that you don't need to re-add subviews. You only need to change the text context of the label. In general, the way to do it correctly is to inherit the UITableViewCell, and initialize the label in awakeFromNib (Which is called only one time when the cell is generated). Then, inside tableView(_:cellForRowAt:) assign new text to the Label, and boolean value to the Switch.

class SwitchTableViewCell: UITableViewCell {

    // MARK: IBOutlets
    var titleLabel: UILabel!
    var toggleSwitch: UISwitch!

    // MARK: Setup - Called one time per cell - That is the key issue.
    override func awakeFromNib() {
        super.awakeFromNib()

        // Init the label and the switch, add them to contentView and set frames / constraints
        titleLabel = UILabel()
        contentView.addSubview(titleLabel)
        // titleLabel.frame = ...
        // ... Do the rest of the initialization
    }
}

This is called every time the cell enters the screen and becomes visible. Your bug is that you add switch and labels to cells that already have them in their layout hierarchy (From previous dequeue).

// Called every time the cell dequeued and becomes visible
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "SwitchTableViewCell") as! SwitchTableViewCell
    cell.titleLabel.text = "Hi There!"
    return cell
}

Don't forget to register your nib to the cell object.

tableView.register(UINib(nibName: "SwitchTableViewCell", bundle: nil), forCellReuseIdentifier: "SwitchTableViewCell")

B.T.W: If you are using a nib file, you can already add the label and the switch in the nib itself. If not, My suggestion might be a better solution.

Upvotes: 1

rmaddy
rmaddy

Reputation: 318914

Why add your own label? Just use the provided textLabel of a cell.

And a simpler way to add a switch to the cell is to set the switch as the accessoryView.

You are also setting up your switch listener for the wrong event. And you need to keep track of the state of each switch.

class ViewController: UITableViewController {
    var switchStates = [ false, false ]

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "MyCellID", for: indexPath)

        cell.textLabel.text = indexPath.row == 0 ? "First Label" : "Second Label"

        let sw = UISwitch()
        sw.isOn = switchStates[indexPath.row]
        sw.tag = indexPath.row + 1 // avoid using 0 for a tag
        sw.addTarget(self, action: #selector(stateChanged), for: .valueChanged)
        cell.accessoryView = sw

        return cell
    }

    func stateChanged(sender : UISwitch) {
        // Update state for this switch
        switchStates[sender.tag - 1] = sender.isOn

        // act on switch change as needed
    }
}

Upvotes: 1

Related Questions