I've been coding for some time now and since I am working alone I'm not strict in my coding structure approach(bad idea) and was only focused on getting things done which resulted in MVC(MASSIVE-View-Controllers). Now recently I've started adapting to MVVM and things are looking clean and simple. But I'm not sure if I'm following or breaking the MVVM pattern.
Please review the code below.
MyActivityModel(Model)
// MARK: - Request
struct MyActivityRequest: Convertable{
var UserID = UD.string(forKey: USER_ID) ?? ""
var TransactionType = ActivityType.ALL.rawValue
}
// MARK: - Response
struct MyActivityResponse: Codable {
let Value: [MyActivityObject]?
let Response: String?
let ResponseCode: Int?
}
struct MyActivityObject: Codable {
let Date, Points: String?
let TransactionType, Amount: String?
let BranchTitle, TransactionCode: String?
}
ActivityVC(View)
import UIKit
class MyActivityVC:UIViewController{
var activityType = ActivityType.ALL
let vm = MyActivityVM()
let common = Common()
var activityView: UIView!
@IBOutlet weak var noInternetLabel: UILabel!
@IBOutlet weak var noInternetView: UIView!
@IBOutlet weak var noDataLabel: UILabel!
@IBOutlet weak var noDataAvailableView: UIView!
@IBOutlet weak var tabelView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Transactions".localized()
SetupViews()
BindViews()
LoadData()
}
func SetupViews(){
activityView = common.GetActivityView(holderView: self.view)
noInternetLabel.text = "No active internet connection".localized()
noDataLabel.text = "You don't have any transactions yet".localized()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
func BindViews(){
vm.activities.bind { [weak self] in
if $0.count == 0 {
self?.tabelView.isHidden = true
self?.noDataAvailableView.isHidden = false
}else{
self?.tabelView.isHidden = false
self?.noDataAvailableView.isHidden = true
self?.tabelView.reloadData()
}
}
vm.loading.bind { [weak self] in
self?.activityView.isHidden = !$0
}
vm.errorMessage.bind { [weak self] in
guard let self else {return}
if $0.errorMessage.count > 0 {
if $0 == .noInternet{
self.noInternetView.isHidden = false
self.noDataAvailableView.isHidden = true
self.tabelView.isHidden = true
}else{
self.common.ShowDialog(_title: nil, _msg: $0.errorMessage, _view: self)
}
}
}
}
func LoadData(){
vm.LoadActivitiesData(activityType: activityType)
}
}
extension MyActivityVC: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return vm.activities.value.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: MyActivityCell.ID) as? MyActivityCell
else {
return UITableViewCell()
}
cell.activity = vm.activities.value[indexPath.row]
return cell
}
}
MyActivityCell(View)
import UIKit
class MyActivityCell: UITableViewCell {
static let ID = "activity_cell"
@IBOutlet weak var branchTitle: UILabel!
@IBOutlet weak var pointsLabel: UILabel!
@IBOutlet weak var USDLabel: UILabel!
@IBOutlet weak var label: UILabel!
@IBOutlet weak var label2: UILabel!
@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var activityTypeView: UIView!
var activity : MyActivityObject? {
didSet{
branchTitle.text = activity?.BranchTitle
pointsLabel.text = activity?.Points
USDLabel.text = activity?.Amount
dateLabel.text = activity?.Date
guard let type = Int(activity?.TransactionCode ?? "00") else {return}
var color = UIColor.gray
switch type {
case 1:
color = .systemGreen
case 2:
color = .systemRed
case 3:
color = .systemYellow
default:
color = .gray
}
activityTypeView.backgroundColor = color
}
}
override func awakeFromNib() {
super.awakeFromNib()
label.text = "PTS".localized()
label2.text = "USD".localized()
}
}
MyActivityVM(Viewmodel)
import Foundation
class MyActivityVM{
var request = MyActivityRequest()
var activities: Observable<[MyActivityObject]> = Observable([MyActivityObject]())
var loading: Observable<Bool> = Observable(false)
var errorMessage: Observable<RequestErrorHandling> = Observable(RequestErrorHandling.none)
func LoadActivitiesData(activityType: ActivityType){
request.TransactionType = activityType.rawValue
guard let body = request.convertToDict() else {return}
loading.value = true
URLSession.shared.Request(params: "activities",
body: body,
expecting: MyActivityResponse.self){result in
DispatchQueue.main.async{ [weak self] in
guard let self else {return}
self.loading.value = false
switch result {
case .success(let data):
if data.Response == "success"{
guard let transactions = data.Value else {
return
}
self.activities.value.append(contentsOf: transactions)
}
case .failure(let failure):
self.errorMessage.value = failure as! RequestErrorHandling
}
}
}
}
}
I've omitted the URLSession extension to create and fire the API request. I'm not using callback/closures because the views are bind using Boxing here.
Any comments are appreciated.
PS Not sure if this is the correct place to ask this question so please don't down vote.