Reputation: 613
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
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:
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