Search code examples
iosjsonswiftdelegatesviewcontroller

How to properly implement protocols and delegates between different view controllers in Swift?


So I am new to protocols and delegate. However I tried to use one in my project after learning about it. I'm not sure if I did anything wrong. But I can't update the label.


import UIKit

class CountryVC: UIViewController {
    //MARK:-Properties   
    let deathLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.textAlignment = .center
        label.text = "I am Death"
        return label
    }()
    let recoveredLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.textAlignment = .center
        return label
    }()
    let infectedLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.textAlignment = .center
        return label
    }()
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        view.addSubview(deathsView)
        view.addSubview(infectedView)
        view.addSubview(recoveredView)

        deathsView.anchor(top: view.topAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 100, paddingLeft: 20, paddingRight: 20, height: 100)
        deathsView.addSubview(deathLabel)
        deathLabel.anchor()
        deathLabel.centerXAnchor.constraint(equalTo: deathsView.centerXAnchor).isActive = true
        deathLabel.centerYAnchor.constraint(equalTo: deathsView.centerYAnchor).isActive = true

        infectedView.anchor(top: deathsView.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 20, paddingLeft: 20, paddingRight: 20, height: 100)
        deathsView.addSubview(infectedLabel)
        infectedLabel.anchor()
        infectedLabel.centerXAnchor.constraint(equalTo: infectedView.centerXAnchor).isActive = true
        infectedLabel.centerYAnchor.constraint(equalTo: infectedView.centerYAnchor).isActive = true       
        recoveredView.anchor(top: infectedView.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 20, paddingLeft: 20, paddingRight: 20, height: 100)
        recoveredView.addSubview(recoveredLabel)
        recoveredLabel.anchor()
        recoveredLabel.centerXAnchor.constraint(equalTo: recoveredView.centerXAnchor).isActive = true
        recoveredLabel.centerYAnchor.constraint(equalTo: recoveredView.centerYAnchor).isActive = true
    }
}

extension CountryVC: UpdateCountry {
    func update(allCases: Int, recovered: Int, deaths: Int) {
        DispatchQueue.main.async {
            self.deathLabel.text = String(deaths)
            self.recoveredLabel.text = String(recovered)
            self.infectedLabel.text = String(allCases)
        }
        print (String(deaths))
        print(String(recovered))
        print(String(allCases))
    }
}

I can see the data getting passed as I can print the datas. I called the update function in my other table view controller to get the data like this.


class CountryTableControllerTableViewController: UITableViewController {
    //MARK:- Properties
    var delegate: UpdateCountry!
    //MARK:- Initialization
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = CountryVC()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
        self.navigationItem.title = "Select Your Country"
    }
    // MARK: - Table view data source
    override func numberOfSections(in tableView: UITableView) -> Int { 1 }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { countries.count }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
        cell.textLabel?.text = countries[indexPath.row]
        return cell
    }
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let urlCountry = "https://corona.lmao.ninja/countries/\(countries[indexPath.row])"
        let acturalUrl = urlCountry.lowercased()
        guard let url = URL(string: acturalUrl) else { return }
        let controler = CountryVC()
        controler.modalPresentationStyle = .fullScreen
        present(controler, animated: true, completion: nil)
        URLSession.shared.dataTask(with: url) {(data, response, error) in
            guard let data = data else {return}
            do {
                let allCases = try JSONDecoder().decode(CountryCases.self, from: data)
                self.delegate.update(allCases: allCases.cases, recovered: allCases.recovered, deaths: allCases.deaths)  
            } catch let jsonErr {
                print("Error serializing json", jsonErr)
            }
        }.resume()     
    }
}

The controller is presented with the data but the label isn't updated in the countryVC.


Solution

  • First create your protocol as follow:

    protocol CountryViewControllerDelegate: class {
        func updateLabels(_ controller: CountryViewController, country: String, allCases: Int, recovered: Int, deaths: Int)
    }
    

    Then in your CountryViewController:

    class CountryViewController: UIViewController {
        weak var delegate: CountryViewControllerDelegate?
        // code ...
    }
    

    extension CountryViewController: CountryViewControllerDelegate {
        func updateLabels(_ controller: CountryViewController, country: String,  allCases: Int, recovered: Int, deaths: Int) {
            print("CountryViewController:", #function)
            DispatchQueue.main.async {
    
                // update your labels in the main thread 
                // self.deathLabel.text = String(deaths)
                // self.recoveredLabel.text = String(recovered)
                // self.infectedLabel.text = String(allCases)
    
                print("Country:", country)
                print("deaths:", deaths)
                print("recovered:", recovered)
                print("infected:", allCases)
            }
        }
    }
    

    Finally in your CountryTableViewController:

    class CountryTableViewController: UITableViewController, CountryViewControllerDelegate { 
    
        weak var delegate: CountryViewControllerDelegate?
        // code ...
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.navigationItem.title = "Select Your Country"
            delegate = self
        }
    
        // code ...
    
        override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            let country = countries[indexPath.row]
            let string = "https://corona.lmao.ninja/countries/\(country.lowercased())"
            guard let url = URL(string: link) else { return }
            let storyBoard = UIStoryboard(name: "Main", bundle: nil)
            let controller = storyBoard.instantiateViewController(withIdentifier: "CountryViewController") as! CountryViewController
            controller.delegate = self
            controller.modalPresentationStyle = .pageSheet
            controller.title = country
            let countryNavigationController = UINavigationController(rootViewController: controller)
            countryNavigationController.viewControllers = [controller]
            self.present(countryNavigationController, animated: true)
    
            URLSession.shared.dataTask(with: url) { data, response, error  in
                guard let data = data else { return }
                if let string = String(data: data, encoding: .utf8), string == "Country not found"  {
                    self.updateLabels(controller, country: country, allCases: 0, recovered: 0, deaths: 0)
                    return
                }
                do {
                    let allCases = try JSONDecoder().decode(CountryCases.self, from: data)
                    self.updateLabels(controller, country: country, allCases: allCases.cases, recovered: allCases.recovered, deaths: allCases.deaths)
                } catch {
                    print("JSON error:", error)
                }
            }.resume()
        }
        func updateLabels(_ controller: CountryViewController, country: String, allCases: Int, recovered: Int, deaths: Int) {
            print("CountryTableViewController:", #function)
            controller.updateLabels(controller, country: country, allCases: allCases, recovered: recovered, deaths: deaths)
        }
    }
    

    enter image description here enter image description here

    Sample Project