Home > Software engineering >  How to add UISwitch to a specific UITableView cell programmatically
How to add UISwitch to a specific UITableView cell programmatically

Time:09-07

I am relatively new to UIKit. Currently, I am trying to create a UISwitch that will show up on a specific UITableView cell. However, I can't seem to figure out how to do this. Instead, I am getting a UISwitch on every single cell in the UITableView.

My code is below:

import UIKit

class SettingsVC: UIViewController {
    
    var tableView = UITableView(frame: .zero, style: .insetGrouped)
    let cells = ["Change Accent Color", "Change Currency Symbol", "Vibrations"]
    let cellReuseIdentifier = "cell"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        createTableView()
        setTableViewDelegates()
    }
    
    func createTableView() {
        view.addSubview(tableView)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
        
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    }
    
    func setTableViewDelegates() {
        tableView.delegate = self
        tableView.dataSource = self
    }
}

extension SettingsVC: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cells.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
            return UITableViewCell()
        }
        
        cell.textLabel?.text = cells[indexPath.row]
        
        let switchView = UISwitch(frame: .zero)
        switchView.setOn(false, animated: true)
        
        cell.accessoryView = switchView
        
        return cell
    }
}

This is how my UITableView looks currently in the simulator.

ScreenShot

This is how I would like the UITableView to look.

ScreenShot

How would I be able to achieve the look I'm going for? Any help would be greatly appreciated.

CodePudding user response:

The method tableView(_:cellForRowAt:) is used to create all cells for a table, so the code inside this method is called for each cell. You need to figure out a condition that distinguishes the cell with a UISwitch and run the corresponding piece conditionally. Conceptually, something like this:

func tableView(_ tableView: UITableView,
               cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
    return UITableViewCell()
  }

  cell.textLabel?.text = cells[indexPath.row]

  if isSwitchNeeded { // Here.
    let switchView = UISwitch(frame: .zero)
    switchView.setOn(false, animated: true)
  }

  cell.accessoryView = switchView
        
  return cell
}

There are some architectural options that might allow you do that. One of them is to rely on the index path. For instance, this should work in your raw example:

func tableView(_ tableView: UITableView,
               cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
    return UITableViewCell()
  }

  cell.textLabel?.text = cells[indexPath.row]

  if indexPath.row == 2 {
    let switchView = UISwitch(frame: .zero)
    switchView.setOn(false, animated: true)
  }

  cell.accessoryView = switchView
        
  return cell
}

And a million other ways.

CodePudding user response:

First of all most likely you want to save the value of the switch, so create a property on the top level of the view controller

var enableVibrations = false

Second of all cells are reused. Even if there are only three cells it's good practice to set all UI elements to a defined state, that means to set the accessory view to nil if there is no switch.

And there is a dequeueReusableCell API which returns a non-optional cell.

func tableView(_ tableView: UITableView,
               cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
  let title = cells[indexPath.row]
  cell.textLabel?.text = title
  if title == "Vibrations" {
     let switchView = UISwitch(frame: .zero)
     switchView.setOn(enableVibrations, animated: true)
     switchView.addTarget(self, action: #selector(toggleVibrations), for: .valueChanged)
     cell.accessoryView = switchView
  } else {
     cell.accessoryView = nil
  }    
  return cell
}

And add the action method

@objc func toggleVibrations(_ sender : UISwitch) {
    self.enableVibrations = sender.isOn
}
  • Related