DEV Community

Avelyn Hyunjeong Choi
Avelyn Hyunjeong Choi

Posted on • Updated on

Advanced UITableView - Closure

1.Create a subclass named EmpTVCell
Cmd+N > Cocoa Touch Class > select UITableViewCell
step 1

class EmpTVCell: UITableViewCell {
    @IBOutlet weak var labelEmpName: UILabel!
    @IBOutlet weak var labelEmpTeam: UILabel!

    // type function
    // you can assign a function expression to the variable
    // empty block by default. we will implement this in main class
    var transferEmployee: (() -> ()) = {}

    // whenever user presses the button -> call this function
    @IBAction func transferEmployee(_ sender: Any) {
        transferEmployee()
    }
}
Enter fullscreen mode Exit fullscreen mode

*NOTE: if you want to see the storyboard on the side, click button below +
editor on the right

2.Assign identifier to the prototype cell, empCell
identifier

3.Change class name to EmpTVCell we created earlier
empTVCell

4.Connect the UITableView with datasource and delegate

class ViewController: UIViewController {

    @IBOutlet weak var employeeTableView: UITableView!

    var employees: [Employee] = [
      Employee(name: "John", team: "Frontend"),
      Employee(name: "Jane", team: "Backend"),
      Employee(name: "Mike", team: "Product"),
      Employee(name: "Sarah", team: "Design"),
      Employee(name: "Tom", team: "Testing")
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        employeeTableView.dataSource = self
        employeeTableView.delegate = self
    }
}
Enter fullscreen mode Exit fullscreen mode

In this step, we can create an extension and add both UITableViewDataSource, UITableViewDelegate in the extension itself as below.

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return employees.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // custom ui table view cell
        // you have to let class know that this is the custome class of ui table view cell!
        // so you have to specify custom subclass of ui table view cell
        // without type casting to EmpTVcell -> it will be treated as normal ui table view cell
        let cell = tableView.dequeueReusableCell(withIdentifier: "empCell", for: indexPath) as! EmpTVCell

        // now you can call labelEmpName and Team of subclass of ui table view cell = EmpTVCell because of the type casting above
        cell.labelEmpName.text = employees[indexPath.row].name
        cell.labelEmpTeam.text = employees[indexPath.row].team

        // now I can now provide a new block!
        // this is a button press event happened from another subclass (EmpTVCell subclass)
        // closure = implement between two classes
        cell.transferEmployee = {
            // transfer logic here
            print("Hello, \(self.employees[indexPath.row].name)")

            self.changeTeam(for: indexPath.row) {
                // reload table view for updating the rows again! it still won't work if we don't have completion handler
                // this will happen even before user enters an input
                // we only reload table after we have updated a team in the data source
                tableView.reloadRows(at: [indexPath], with: .automatic)
            }
        }
        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 86
    }
}
Enter fullscreen mode Exit fullscreen mode

*NOTE: anything you do here is treated like you did to main class itself

5.Implement a button click handler (transferEmployee - empty in the subclass, EmpTVCell)

class ViewController: UIViewController {
    ...
    func changeTeam(for index: Int, completion: @escaping (() -> Void)) {
        // create an alert = async, we need to wait till the action is completed before relaoading tabie View rows
        // @escaping is used to inform callers of a function that takes a closure
        // that the closure might be stored or otherwise outlive the scope of the receiving function.
        let alert = UIAlertController(title: "Team Transfer", message: "Select the team you want to transfer to", preferredStyle: .actionSheet)

        // enum Team
        for team in Team.allCases {
        // create an action
          let action = UIAlertAction(title: team.rawValue, style: .default) { _ in
            self.employees[index].team = team.rawValue // Frontend, Backend, Product...
              // add completion handler after I have updated data source
              completion()  // invoke our own completion handler -> reloadRows
          }

        // add it to the alert
          alert.addAction(action)
        }

        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))

        // present this alert
        present(alert, animated: true)
    }
}
Enter fullscreen mode Exit fullscreen mode

Demo

Image description

Image description

Image description

Top comments (0)