skip to Main Content

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.

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search