DEV Community

Avelyn Hyunjeong Choi
Avelyn Hyunjeong Choi

Posted on • Edited 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

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Sentry growth stunted Image

If you are wasting time trying to track down the cause of a crash, it’s time for a better solution. Get your crash rates to zero (or close to zero as possible) with less time and effort.

Try Sentry for more visibility into crashes, better workflow tools, and customizable alerts and reporting.

Switch Tools