Ace
Ace

Reputation: 613

Why is my Swift programmatic tableview not populating?

I have been practicing doing Swift programmatic UI and MVVM. I tried to follow the following tutorial: https://medium.com/flawless-app-stories/mvvm-in-ios-swift-aa1448a66fb4 and also I added a tab bar and settings view controller just for practice. My issue is in my HomeView controller I see the data is being obtained by my table is displaying empty cells.

HomeViewController:

import Foundation
import UIKit

class HomeViewController: UIViewController{

private var employeeViewModel: EmployeeViewModel!
var employeeTableView: UITableView = UITableView()
private var datasource: EmployeeTableViewDataSource<EmployeeTableViewCell, EmployeeData>!


override func viewDidLoad() {
    // some code here
    configure()
    callToViewModelForUIUpdate()
}

func configure(){
    self.title = "Home"
    self.view.backgroundColor = .lightGray
    let width = self.view.frame.width
    
    let navigationBar: UINavigationBar = UINavigationBar(frame: CGRect(x: 0, y: 0, width: width, height: 44))
    let navigationItem = UINavigationItem(title: "Home")
    navigationBar.setItems([navigationItem], animated: false)
    self.view.addSubview(navigationBar)
    
    let screenSize: CGRect = UIScreen.main.bounds
    // get the screens width and height
    let screenWidth =  screenSize.width
    let screenHeight = screenSize.height
    // set the table to the screens width and height
    employeeTableView.frame = CGRect(x: 0,y: 0, width: screenWidth, height: screenHeight)

    //register the cell to the table
    employeeTableView.register(EmployeeTableViewCell.self, forCellReuseIdentifier: "EmployeeTableViewCell")
    //add the table to the view
    self.view.addSubview(employeeTableView)
    
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    
}

func callToViewModelForUIUpdate(){
    self.employeeViewModel = EmployeeViewModel()
    self.employeeViewModel.bindEmployeeViewModelToController = {
        self.updateDataSource()
    }
    
}

func updateDataSource(){
    
    self.datasource = EmployeeTableViewDataSource(cellIdentifier: "EmployeeTableViewCell", items: self.employeeViewModel.empData.data, configureCell: { (cell, evm) in
        cell.employeeIdLabel.text = "\(evm.id)"
        cell.employeeNameLabel.text = evm.employeeName
    })
    
    DispatchQueue.main.async {
        self.employeeTableView.dataSource = self.datasource
        self.employeeTableView.reloadData()
    }
}

 }

EmployeeViewModel:

import Foundation

class EmployeeViewModel: NSObject{
    
    private var apiService: APIService!
    private(set) var empData: Employee!{
        didSet{
            self.bindEmployeeViewModelToController()
        }
    }
    
    var bindEmployeeViewModelToController: (() -> ()) = {}
    
    override init(){
        super.init()
        self.apiService = APIService()
        callFuncToGetEmpData()
        
    }
    
    func callFuncToGetEmpData(){
        self.apiService.apiToGetEmployeeData{(empData) in
            self.empData = empData
        }
    }
    
    
}

EmployeeTableViewCell:

    import Foundation
import UIKit

class EmployeeTableViewCell: UITableViewCell{
    
    var employeeIdLabel = UILabel()
    var employeeNameLabel = UILabel()
    
    var employee: EmployeeData?{
        didSet{
            employeeIdLabel.text = String(describing: employee?.id)
            employeeNameLabel.text = employee?.employeeName
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
}

APIService:

    import Foundation

class APIService: NSObject{
    
    private let source = URL(string: "http://dummy.restapiexample.com/api/v1/employees")!
    
    func apiToGetEmployeeData(completion: @escaping (Employee)-> ()){
        URLSession.shared.dataTask(with: source){(data, urlResponse, error) in
            if let data = data{
                let jsonDecoder = JSONDecoder()
                
                let empData = try! jsonDecoder.decode(Employee.self, from: data)
                completion(empData)
            }
        }.resume()
    }
    
}

EmployeeTableViewDataSource:

import Foundation
import UIKit

class EmployeeTableViewDataSource<CELL : UITableViewCell,T> : NSObject, UITableViewDataSource {
    
    private var cellIdentifier : String!
    private var items : [T]!
    var configureCell : (CELL, T) -> () = {_,_ in }
    
    
    init(cellIdentifier : String, items : [T], configureCell : @escaping (CELL, T) -> ()) {
        self.cellIdentifier = cellIdentifier
        self.items =  items
        self.configureCell = configureCell
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
         let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CELL
        
        let item = self.items[indexPath.row]
        self.configureCell(cell, item)
        return cell
    }
}

EmployeeModel:

 import Foundation


// MARK: - Welcome
struct Employee: Decodable {
    let status: String
    let data: [EmployeeData]
}

// MARK: - Datum
struct EmployeeData: Decodable {
    let id, employeeSalary, employeeAge: Int
    let employeeName: String
    let profileImage: String

    enum CodingKeys: String, CodingKey {
        case id
        case employeeName = "employee_name"
        case employeeSalary = "employee_salary"
        case employeeAge = "employee_age"
        case profileImage = "profile_image"
    }
}

ViewController:

    import UIKit

class ViewController: UITabBarController, UITabBarControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.delegate = self
    }

    override func viewWillAppear(_ animated: Bool) {
        configureTabBar()
    }
    
    func configureTabBar(){
        //createFirstTab view controller
        let firstTab = HomeViewController()
        // create nav controller to embed view controller in
        let navVc = UINavigationController(rootViewController: firstTab)
        // set the title and image at the bottom tab bar for the 1st VC
        let firstItem = UITabBarItem(title: "Home", image: UIImage(systemName: "pencil"), selectedImage: UIImage(systemName: "pencil"))
        //set the item to pertain to the first tab
        firstTab.tabBarItem = firstItem
        
        //createSecondTab
        let secondTab = SettingsViewController()
        let secondNavVC = UINavigationController(rootViewController: secondTab)
        let secondItem = UITabBarItem(title: "Settings", image: UIImage(systemName: "More"), selectedImage: UIImage(systemName: "More"))
        secondTab.tabBarItem = secondItem
        
        //add the VCs as VCs of the tab bar
        self.viewControllers = [navVc, secondNavVC]
        
    }
    
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        print("Selected \(viewController.title)")
    }

}

Everything populates and navigation is fine but the tableview is just empty.

Upvotes: 0

Views: 46

Answers (1)

DonMag
DonMag

Reputation: 77433

In the code you posted, I don't see anywhere that you are adding the employeeIdLabel and employeeNameLabel labels to the cell's view hierarchy.

Try using this cell class:

class EmployeeTableViewCell: UITableViewCell{
    
    var employeeIdLabel = UILabel()
    var employeeNameLabel = UILabel()
    
    var employee: EmployeeData?{
        didSet{
            employeeIdLabel.text = String(describing: employee?.id)
            employeeNameLabel.text = employee?.employeeName
        }
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {

        employeeIdLabel.backgroundColor = .green
        employeeNameLabel.backgroundColor = .yellow
        contentView.addSubview(employeeIdLabel)
        contentView.addSubview(employeeNameLabel)
        employeeIdLabel.translatesAutoresizingMaskIntoConstraints = false
        employeeNameLabel.translatesAutoresizingMaskIntoConstraints = false
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            employeeIdLabel.topAnchor.constraint(equalTo: g.topAnchor),
            employeeIdLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            employeeIdLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            
            employeeNameLabel.topAnchor.constraint(equalTo: employeeIdLabel.bottomAnchor, constant: 8.0),
            employeeNameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            employeeNameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            
            employeeNameLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ])

    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
}

Result:

enter image description here

As a side note, make sure you gracefully handle a failed attempt to get the data:

class APIService: NSObject{
    
    private let source = URL(string: "http://dummy.restapiexample.com/api/v1/employees")!
    
    func apiToGetEmployeeData(completion: @escaping (Employee)-> ()){
        URLSession.shared.dataTask(with: source){(data, urlResponse, error) in
            if let data = data{
                let jsonDecoder = JSONDecoder()
                
                do {
                    let empData = try jsonDecoder.decode(Employee.self, from: data)
                    completion(empData)
                } catch {
                    print("Failed to get data!")
                    // show an alert or something...
                }

            }
        }.resume()
    }
    
}

Upvotes: 1

Related Questions