├── MVVMExample ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 1024.png │ │ ├── 120.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ └── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── primaryColor.colorset │ │ └── Contents.json │ └── secondaryColor.colorset │ │ └── Contents.json ├── Screens │ └── Employees │ │ ├── ViewModel │ │ ├── EmployeeCellViewModel.swift │ │ └── EmployeesViewModel.swift │ │ ├── Model │ │ └── Employee.swift │ │ └── View │ │ ├── Cell │ │ ├── EmployeeCell.swift │ │ └── EmployeeCell.xib │ │ └── EmployeesViewController.swift ├── Services │ └── EmployeesService.swift ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── Helpers │ └── HttpRequestHelper.swift └── SceneDelegate.swift ├── README.md ├── MVVMExample.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── johncodeos.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── johncodeos.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj └── demo.json /MVVMExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://i.imgur.com/Dv73hCk.png) 2 | # MVVMiOSExample 3 | Implement MVVM pattern with Swift in iOS 4 | 5 | https://johncodeos.com/how-to-implement-mvvm-pattern-with-swift-in-ios/ 6 | -------------------------------------------------------------------------------- /MVVMExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MVVMExample.xcodeproj/xcuserdata/johncodeos.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /MVVMExample.xcodeproj/project.xcworkspace/xcuserdata/johncodeos.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/HEAD/MVVMExample.xcodeproj/project.xcworkspace/xcuserdata/johncodeos.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MVVMExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MVVMExample/Screens/Employees/ViewModel/EmployeeCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeeCellViewModel.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import Foundation 9 | 10 | struct EmployeeCellViewModel { 11 | var id: String 12 | var name: String 13 | var salary: String 14 | var age: String 15 | } 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/primaryColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.294", 9 | "green" : "0.133", 10 | "red" : "0.737" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/secondaryColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.251", 9 | "green" : "0.110", 10 | "red" : "0.620" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MVVMExample.xcodeproj/xcuserdata/johncodeos.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MVVMExample.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MVVMExample/Screens/Employees/Model/Employee.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Employee.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import Foundation 9 | 10 | typealias Employees = [Employee] 11 | 12 | // MARK: - Employee 13 | struct Employee: Codable { 14 | let id: String 15 | let employeeName: String 16 | let employeeSalary: String 17 | let employeeAge: String 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case id 21 | case employeeName = "employee_name" 22 | case employeeSalary = "employee_salary" 23 | case employeeAge = "employee_age" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MVVMExample/Services/EmployeesService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeesService.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol EmployeesServiceProtocol { 11 | func getEmployees(completion: @escaping (_ success: Bool, _ results: Employees?, _ error: String?) -> ()) 12 | } 13 | 14 | class EmployeesService: EmployeesServiceProtocol { 15 | func getEmployees(completion: @escaping (Bool, Employees?, String?) -> ()) { 16 | HttpRequestHelper().GET(url: "https://raw.githubusercontent.com/johncodeos-blog/MVVMiOSExample/main/demo.json", params: ["": ""], httpHeader: .application_json) { success, data in 17 | if success { 18 | do { 19 | let model = try JSONDecoder().decode(Employees.self, from: data!) 20 | completion(true, model, nil) 21 | } catch { 22 | completion(false, nil, "Error: Trying to parse Employees to model") 23 | } 24 | } else { 25 | completion(false, nil, "Error: Employees GET Request failed") 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MVVMExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 13 | // Override point for customization after application launch. 14 | return true 15 | } 16 | 17 | // MARK: UISceneSession Lifecycle 18 | 19 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 20 | // Called when a new scene session is being created. 21 | // Use this method to select a configuration to create the new scene with. 22 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 23 | } 24 | 25 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 26 | // Called when the user discards a scene session. 27 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 28 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MVVMExample/Screens/Employees/View/Cell/EmployeeCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeeCell.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class EmployeeCell: UITableViewCell { 11 | @IBOutlet weak var idLabel: UILabel! 12 | @IBOutlet weak var nameLabel: UILabel! 13 | @IBOutlet weak var salaryLabel: UILabel! 14 | @IBOutlet weak var ageLabel: UILabel! 15 | 16 | static var identifier: String { return String(describing: self) } 17 | static var nib: UINib { return UINib(nibName: identifier, bundle: nil) } 18 | 19 | var cellViewModel: EmployeeCellViewModel? { 20 | didSet { 21 | idLabel.text = cellViewModel?.id 22 | nameLabel.text = cellViewModel?.name 23 | salaryLabel.text = cellViewModel?.salary 24 | ageLabel.text = cellViewModel?.age 25 | } 26 | } 27 | 28 | override func awakeFromNib() { 29 | super.awakeFromNib() 30 | initView() 31 | } 32 | 33 | func initView() { 34 | // Cell view customization 35 | backgroundColor = .clear 36 | 37 | // Line separator full width 38 | preservesSuperviewLayoutMargins = false 39 | separatorInset = UIEdgeInsets.zero 40 | layoutMargins = UIEdgeInsets.zero 41 | } 42 | 43 | override func prepareForReuse() { 44 | super.prepareForReuse() 45 | idLabel.text = nil 46 | nameLabel.text = nil 47 | salaryLabel.text = nil 48 | ageLabel.text = nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MVVMExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MVVMExample/Screens/Employees/ViewModel/EmployeesViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeesViewModel.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import Foundation 9 | 10 | class EmployeesViewModel: NSObject { 11 | private var employeeService: EmployeesServiceProtocol 12 | 13 | var reloadTableView: (() -> Void)? 14 | 15 | var employees = Employees() 16 | 17 | var employeeCellViewModels = [EmployeeCellViewModel]() { 18 | didSet { 19 | reloadTableView?() 20 | } 21 | } 22 | 23 | init(employeeService: EmployeesServiceProtocol = EmployeesService()) { 24 | self.employeeService = employeeService 25 | } 26 | 27 | func getEmployees() { 28 | employeeService.getEmployees { success, model, error in 29 | if success, let employees = model { 30 | self.fetchData(employees: employees) 31 | } else { 32 | print(error!) 33 | } 34 | } 35 | } 36 | 37 | func fetchData(employees: Employees) { 38 | self.employees = employees // Cache 39 | var vms = [EmployeeCellViewModel]() 40 | for employee in employees { 41 | vms.append(createCellModel(employee: employee)) 42 | } 43 | employeeCellViewModels = vms 44 | } 45 | 46 | func createCellModel(employee: Employee) -> EmployeeCellViewModel { 47 | let id = employee.id 48 | let name = employee.employeeName 49 | let salary = "$ " + employee.employeeSalary 50 | let age = employee.employeeAge 51 | 52 | return EmployeeCellViewModel(id: id, name: name, salary: salary, age: age) 53 | } 54 | 55 | func getCellViewModel(at indexPath: IndexPath) -> EmployeeCellViewModel { 56 | return employeeCellViewModels[indexPath.row] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MVVMExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /MVVMExample/Helpers/HttpRequestHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HttpRequestHelper.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import Foundation 9 | 10 | enum HTTPHeaderFields { 11 | case application_json 12 | case application_x_www_form_urlencoded 13 | case none 14 | } 15 | 16 | class HttpRequestHelper { 17 | func GET(url: String, params: [String: String], httpHeader: HTTPHeaderFields, complete: @escaping (Bool, Data?) -> ()) { 18 | guard var components = URLComponents(string: url) else { 19 | print("Error: cannot create URLCompontents") 20 | return 21 | } 22 | components.queryItems = params.map { key, value in 23 | URLQueryItem(name: key, value: value) 24 | } 25 | 26 | guard let url = components.url else { 27 | print("Error: cannot create URL") 28 | return 29 | } 30 | var request = URLRequest(url: url) 31 | request.httpMethod = "GET" 32 | 33 | switch httpHeader { 34 | case .application_json: 35 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 36 | case .application_x_www_form_urlencoded: 37 | request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") 38 | case .none: break 39 | } 40 | 41 | 42 | // .ephemeral prevent JSON from caching (They'll store in Ram and nothing on Disk) 43 | let config = URLSessionConfiguration.ephemeral 44 | let session = URLSession(configuration: config) 45 | session.dataTask(with: request) { data, response, error in 46 | guard error == nil else { 47 | print("Error: problem calling GET") 48 | print(error!) 49 | complete(false, nil) 50 | return 51 | } 52 | guard let data = data else { 53 | print("Error: did not receive data") 54 | complete(false, nil) 55 | return 56 | } 57 | guard let response = response as? HTTPURLResponse, (200 ..< 300) ~= response.statusCode else { 58 | print("Error: HTTP request failed") 59 | complete(false, nil) 60 | return 61 | } 62 | complete(true, data) 63 | }.resume() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MVVMExample/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | var window: UIWindow? 12 | 13 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 14 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 15 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 16 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 17 | guard let _ = (scene as? UIWindowScene) else { return } 18 | } 19 | 20 | func sceneDidDisconnect(_ scene: UIScene) { 21 | // Called as the scene is being released by the system. 22 | // This occurs shortly after the scene enters the background, or when its session is discarded. 23 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 24 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 25 | } 26 | 27 | func sceneDidBecomeActive(_ scene: UIScene) { 28 | // Called when the scene has moved from an inactive state to an active state. 29 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 30 | } 31 | 32 | func sceneWillResignActive(_ scene: UIScene) { 33 | // Called when the scene will move from an active state to an inactive state. 34 | // This may occur due to temporary interruptions (ex. an incoming phone call). 35 | } 36 | 37 | func sceneWillEnterForeground(_ scene: UIScene) { 38 | // Called as the scene transitions from the background to the foreground. 39 | // Use this method to undo the changes made on entering the background. 40 | } 41 | 42 | func sceneDidEnterBackground(_ scene: UIScene) { 43 | // Called as the scene transitions from the foreground to the background. 44 | // Use this method to save data, release shared resources, and store enough scene-specific state information 45 | // to restore the scene back to its current state. 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MVVMExample/Screens/Employees/View/EmployeesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmployeesViewController.swift 3 | // MVVMExample 4 | // 5 | // Created by John Codeos on 06/19/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class EmployeesViewController: UIViewController { 11 | @IBOutlet weak var tableView: UITableView! 12 | 13 | lazy var viewModel = { 14 | EmployeesViewModel() 15 | }() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Fix navigation bar color in iOS 15 and above 21 | if #available(iOS 15, *) { 22 | let appearance = UINavigationBarAppearance() 23 | appearance.configureWithOpaqueBackground() 24 | appearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] 25 | appearance.backgroundColor = UIColor(named: "primaryColor") 26 | navigationController?.navigationBar.standardAppearance = appearance 27 | navigationController?.navigationBar.scrollEdgeAppearance = navigationController?.navigationBar.standardAppearance 28 | } 29 | 30 | initView() 31 | initViewModel() 32 | } 33 | 34 | func initView() { 35 | tableView.delegate = self 36 | tableView.dataSource = self 37 | tableView.backgroundColor = UIColor(#colorLiteral(red: 0.6196078431, green: 0.1098039216, blue: 0.2509803922, alpha: 1)) 38 | tableView.separatorColor = .white 39 | tableView.separatorStyle = .singleLine 40 | tableView.tableFooterView = UIView() 41 | tableView.allowsSelection = false 42 | 43 | tableView.register(EmployeeCell.nib, forCellReuseIdentifier: EmployeeCell.identifier) 44 | } 45 | 46 | func initViewModel() { 47 | // Get employees data 48 | viewModel.getEmployees() 49 | 50 | // Reload TableView closure 51 | viewModel.reloadTableView = { [weak self] in 52 | DispatchQueue.main.async { 53 | self?.tableView.reloadData() 54 | } 55 | } 56 | } 57 | } 58 | 59 | // MARK: - UITableViewDelegate 60 | 61 | extension EmployeesViewController: UITableViewDelegate { 62 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 63 | return 130 64 | } 65 | } 66 | 67 | // MARK: - UITableViewDataSource 68 | 69 | extension EmployeesViewController: UITableViewDataSource { 70 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 71 | return viewModel.employeeCellViewModels.count 72 | } 73 | 74 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 75 | guard let cell = tableView.dequeueReusableCell(withIdentifier: EmployeeCell.identifier, for: indexPath) as? EmployeeCell else { fatalError("xib does not exists") } 76 | let cellVM = viewModel.getCellViewModel(at: indexPath) 77 | cell.cellViewModel = cellVM 78 | return cell 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /demo.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "1", 3 | "employee_name": "Tiger Nixon", 4 | "employee_salary": "320800", 5 | "employee_age": "61" 6 | }, 7 | { 8 | "id": "2", 9 | "employee_name": "Garrett Winters", 10 | "employee_salary": "170750", 11 | "employee_age": "63" 12 | }, 13 | { 14 | "id": "3", 15 | "employee_name": "Ashton Cox", 16 | "employee_salary": "86000", 17 | "employee_age": "66" 18 | }, 19 | { 20 | "id": "4", 21 | "employee_name": "Cedric Kelly", 22 | "employee_salary": "433060", 23 | "employee_age": "22" 24 | }, 25 | { 26 | "id": "5", 27 | "employee_name": "Airi Satou", 28 | "employee_salary": "162700", 29 | "employee_age": "33" 30 | }, 31 | { 32 | "id": "6", 33 | "employee_name": "Brielle Williamson", 34 | "employee_salary": "372000", 35 | "employee_age": "61" 36 | }, 37 | { 38 | "id": "7", 39 | "employee_name": "Herrod Chandler", 40 | "employee_salary": "137500", 41 | "employee_age": "59" 42 | }, 43 | { 44 | "id": "8", 45 | "employee_name": "Rhona Davidson", 46 | "employee_salary": "327900", 47 | "employee_age": "55" 48 | }, 49 | { 50 | "id": "9", 51 | "employee_name": "Colleen Hurst", 52 | "employee_salary": "205500", 53 | "employee_age": "39" 54 | }, 55 | { 56 | "id": "10", 57 | "employee_name": "Sonya Frost", 58 | "employee_salary": "103600", 59 | "employee_age": "23" 60 | }, 61 | { 62 | "id": "11", 63 | "employee_name": "Jena Gaines", 64 | "employee_salary": "90560", 65 | "employee_age": "30" 66 | }, 67 | { 68 | "id": "12", 69 | "employee_name": "Quinn Flynn", 70 | "employee_salary": "342000", 71 | "employee_age": "22" 72 | }, 73 | { 74 | "id": "13", 75 | "employee_name": "Charde Marshall", 76 | "employee_salary": "470600", 77 | "employee_age": "36" 78 | }, 79 | { 80 | "id": "14", 81 | "employee_name": "Haley Kennedy", 82 | "employee_salary": "313500", 83 | "employee_age": "43" 84 | }, 85 | { 86 | "id": "15", 87 | "employee_name": "Tatyana Fitzpatrick", 88 | "employee_salary": "385750", 89 | "employee_age": "19" 90 | }, 91 | { 92 | "id": "16", 93 | "employee_name": "Michael Silva", 94 | "employee_salary": "198500", 95 | "employee_age": "66" 96 | }, 97 | { 98 | "id": "17", 99 | "employee_name": "Paul Byrd", 100 | "employee_salary": "725000", 101 | "employee_age": "64" 102 | }, 103 | { 104 | "id": "18", 105 | "employee_name": "Gloria Little", 106 | "employee_salary": "237500", 107 | "employee_age": "59" 108 | }, 109 | { 110 | "id": "19", 111 | "employee_name": "Bradley Greer", 112 | "employee_salary": "132000", 113 | "employee_age": "41" 114 | }, 115 | { 116 | "id": "20", 117 | "employee_name": "Dai Rios", 118 | "employee_salary": "217500", 119 | "employee_age": "35" 120 | }, 121 | { 122 | "id": "21", 123 | "employee_name": "Jenette Caldwell", 124 | "employee_salary": "345000", 125 | "employee_age": "30" 126 | }, 127 | { 128 | "id": "22", 129 | "employee_name": "Yuri Berry", 130 | "employee_salary": "675000", 131 | "employee_age": "40" 132 | }, 133 | { 134 | "id": "23", 135 | "employee_name": "Caesar Vance", 136 | "employee_salary": "106450", 137 | "employee_age": "21" 138 | }, 139 | { 140 | "id": "24", 141 | "employee_name": "Doris Wilder", 142 | "employee_salary": "85600", 143 | "employee_age": "23" 144 | } 145 | ] -------------------------------------------------------------------------------- /MVVMExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "filename" : "40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "idiom" : "iphone", 11 | "size" : "20x20", 12 | "filename" : "60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "size" : "29x29", 18 | "filename" : "58.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "idiom" : "iphone", 23 | "size" : "29x29", 24 | "filename" : "87.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "idiom" : "iphone", 29 | "size" : "40x40", 30 | "filename" : "80.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "40x40", 36 | "filename" : "120.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "idiom" : "iphone", 41 | "size" : "60x60", 42 | "filename" : "120.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "idiom" : "iphone", 47 | "size" : "60x60", 48 | "filename" : "180.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "20x20", 54 | "filename" : "20.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "20x20", 60 | "filename" : "40.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "29x29", 66 | "filename" : "29.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "29x29", 72 | "filename" : "58.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "idiom" : "ipad", 77 | "size" : "40x40", 78 | "filename" : "40.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "idiom" : "ipad", 83 | "size" : "40x40", 84 | "filename" : "80.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "idiom" : "ipad", 89 | "size" : "76x76", 90 | "filename" : "76.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "idiom" : "ipad", 95 | "size" : "76x76", 96 | "filename" : "152.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "idiom" : "ipad", 101 | "size" : "83.5x83.5", 102 | "filename" : "167.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "idiom" : "ios-marketing", 107 | "size" : "1024x1024", 108 | "filename" : "1024.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /MVVMExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /MVVMExample/Screens/Employees/View/Cell/EmployeeCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 66 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /MVVMExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C26BAB1F2573B6F3008C2705 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26BAB1E2573B6F3008C2705 /* AppDelegate.swift */; }; 11 | C26BAB212573B6F3008C2705 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26BAB202573B6F3008C2705 /* SceneDelegate.swift */; }; 12 | C26BAB232573B6F3008C2705 /* EmployeesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26BAB222573B6F3008C2705 /* EmployeesViewController.swift */; }; 13 | C26BAB262573B6F3008C2705 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C26BAB242573B6F3008C2705 /* Main.storyboard */; }; 14 | C26BAB282573B6F4008C2705 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C26BAB272573B6F4008C2705 /* Assets.xcassets */; }; 15 | C26BAB2B2573B6F4008C2705 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C26BAB292573B6F4008C2705 /* LaunchScreen.storyboard */; }; 16 | C26BAB392573C92B008C2705 /* Employee.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26BAB382573C92B008C2705 /* Employee.swift */; }; 17 | C26BAB3C2573C93E008C2705 /* EmployeesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26BAB3B2573C93E008C2705 /* EmployeesViewModel.swift */; }; 18 | C26BAB412573D4A7008C2705 /* HttpRequestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26BAB402573D4A7008C2705 /* HttpRequestHelper.swift */; }; 19 | C26BAB452573D4FD008C2705 /* EmployeesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26BAB442573D4FD008C2705 /* EmployeesService.swift */; }; 20 | C2DE3FCD2574268A00320D6C /* EmployeeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DE3FCB2574268A00320D6C /* EmployeeCell.swift */; }; 21 | C2DE3FCE2574268A00320D6C /* EmployeeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C2DE3FCC2574268A00320D6C /* EmployeeCell.xib */; }; 22 | C2DE3FD12574338300320D6C /* EmployeeCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DE3FD02574338300320D6C /* EmployeeCellViewModel.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | C26BAB1B2573B6F3008C2705 /* MVVMExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MVVMExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | C26BAB1E2573B6F3008C2705 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | C26BAB202573B6F3008C2705 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 29 | C26BAB222573B6F3008C2705 /* EmployeesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmployeesViewController.swift; sourceTree = ""; }; 30 | C26BAB252573B6F3008C2705 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | C26BAB272573B6F4008C2705 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | C26BAB2A2573B6F4008C2705 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 33 | C26BAB2C2573B6F4008C2705 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | C26BAB382573C92B008C2705 /* Employee.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Employee.swift; sourceTree = ""; }; 35 | C26BAB3B2573C93E008C2705 /* EmployeesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmployeesViewModel.swift; sourceTree = ""; }; 36 | C26BAB402573D4A7008C2705 /* HttpRequestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpRequestHelper.swift; sourceTree = ""; }; 37 | C26BAB442573D4FD008C2705 /* EmployeesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmployeesService.swift; sourceTree = ""; }; 38 | C2DE3FCB2574268A00320D6C /* EmployeeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmployeeCell.swift; sourceTree = ""; }; 39 | C2DE3FCC2574268A00320D6C /* EmployeeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmployeeCell.xib; sourceTree = ""; }; 40 | C2DE3FD02574338300320D6C /* EmployeeCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmployeeCellViewModel.swift; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | C26BAB182573B6F3008C2705 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | C26BAB122573B6F3008C2705 = { 55 | isa = PBXGroup; 56 | children = ( 57 | C26BAB1D2573B6F3008C2705 /* MVVMExample */, 58 | C26BAB1C2573B6F3008C2705 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | C26BAB1C2573B6F3008C2705 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | C26BAB1B2573B6F3008C2705 /* MVVMExample.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | C26BAB1D2573B6F3008C2705 /* MVVMExample */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | C26BAB3F2573D466008C2705 /* Helpers */, 74 | C26BAB3E2573CCF4008C2705 /* Services */, 75 | C2DE3FD32574458300320D6C /* Screens */, 76 | C26BAB1E2573B6F3008C2705 /* AppDelegate.swift */, 77 | C26BAB202573B6F3008C2705 /* SceneDelegate.swift */, 78 | C26BAB242573B6F3008C2705 /* Main.storyboard */, 79 | C26BAB272573B6F4008C2705 /* Assets.xcassets */, 80 | C26BAB292573B6F4008C2705 /* LaunchScreen.storyboard */, 81 | C26BAB2C2573B6F4008C2705 /* Info.plist */, 82 | ); 83 | path = MVVMExample; 84 | sourceTree = ""; 85 | }; 86 | C26BAB332573C83F008C2705 /* View */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | C2DE3FCA2574266100320D6C /* Cell */, 90 | C26BAB222573B6F3008C2705 /* EmployeesViewController.swift */, 91 | ); 92 | path = View; 93 | sourceTree = ""; 94 | }; 95 | C26BAB342573C848008C2705 /* ViewModel */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | C2DE3FD02574338300320D6C /* EmployeeCellViewModel.swift */, 99 | C26BAB3B2573C93E008C2705 /* EmployeesViewModel.swift */, 100 | ); 101 | path = ViewModel; 102 | sourceTree = ""; 103 | }; 104 | C26BAB352573C858008C2705 /* Model */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | C26BAB382573C92B008C2705 /* Employee.swift */, 108 | ); 109 | path = Model; 110 | sourceTree = ""; 111 | }; 112 | C26BAB3E2573CCF4008C2705 /* Services */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | C26BAB442573D4FD008C2705 /* EmployeesService.swift */, 116 | ); 117 | path = Services; 118 | sourceTree = ""; 119 | }; 120 | C26BAB3F2573D466008C2705 /* Helpers */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | C26BAB402573D4A7008C2705 /* HttpRequestHelper.swift */, 124 | ); 125 | path = Helpers; 126 | sourceTree = ""; 127 | }; 128 | C2DE3FCA2574266100320D6C /* Cell */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | C2DE3FCB2574268A00320D6C /* EmployeeCell.swift */, 132 | C2DE3FCC2574268A00320D6C /* EmployeeCell.xib */, 133 | ); 134 | path = Cell; 135 | sourceTree = ""; 136 | }; 137 | C2DE3FD32574458300320D6C /* Screens */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | C2DE3FD42574459C00320D6C /* Employees */, 141 | ); 142 | path = Screens; 143 | sourceTree = ""; 144 | }; 145 | C2DE3FD42574459C00320D6C /* Employees */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | C26BAB352573C858008C2705 /* Model */, 149 | C26BAB332573C83F008C2705 /* View */, 150 | C26BAB342573C848008C2705 /* ViewModel */, 151 | ); 152 | path = Employees; 153 | sourceTree = ""; 154 | }; 155 | /* End PBXGroup section */ 156 | 157 | /* Begin PBXNativeTarget section */ 158 | C26BAB1A2573B6F3008C2705 /* MVVMExample */ = { 159 | isa = PBXNativeTarget; 160 | buildConfigurationList = C26BAB2F2573B6F4008C2705 /* Build configuration list for PBXNativeTarget "MVVMExample" */; 161 | buildPhases = ( 162 | C26BAB172573B6F3008C2705 /* Sources */, 163 | C26BAB182573B6F3008C2705 /* Frameworks */, 164 | C26BAB192573B6F3008C2705 /* Resources */, 165 | ); 166 | buildRules = ( 167 | ); 168 | dependencies = ( 169 | ); 170 | name = MVVMExample; 171 | productName = MVVMExample; 172 | productReference = C26BAB1B2573B6F3008C2705 /* MVVMExample.app */; 173 | productType = "com.apple.product-type.application"; 174 | }; 175 | /* End PBXNativeTarget section */ 176 | 177 | /* Begin PBXProject section */ 178 | C26BAB132573B6F3008C2705 /* Project object */ = { 179 | isa = PBXProject; 180 | attributes = { 181 | LastSwiftUpdateCheck = 1220; 182 | LastUpgradeCheck = 1220; 183 | TargetAttributes = { 184 | C26BAB1A2573B6F3008C2705 = { 185 | CreatedOnToolsVersion = 12.2; 186 | }; 187 | }; 188 | }; 189 | buildConfigurationList = C26BAB162573B6F3008C2705 /* Build configuration list for PBXProject "MVVMExample" */; 190 | compatibilityVersion = "Xcode 9.3"; 191 | developmentRegion = en; 192 | hasScannedForEncodings = 0; 193 | knownRegions = ( 194 | en, 195 | Base, 196 | ); 197 | mainGroup = C26BAB122573B6F3008C2705; 198 | productRefGroup = C26BAB1C2573B6F3008C2705 /* Products */; 199 | projectDirPath = ""; 200 | projectRoot = ""; 201 | targets = ( 202 | C26BAB1A2573B6F3008C2705 /* MVVMExample */, 203 | ); 204 | }; 205 | /* End PBXProject section */ 206 | 207 | /* Begin PBXResourcesBuildPhase section */ 208 | C26BAB192573B6F3008C2705 /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | C26BAB2B2573B6F4008C2705 /* LaunchScreen.storyboard in Resources */, 213 | C2DE3FCE2574268A00320D6C /* EmployeeCell.xib in Resources */, 214 | C26BAB282573B6F4008C2705 /* Assets.xcassets in Resources */, 215 | C26BAB262573B6F3008C2705 /* Main.storyboard in Resources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXSourcesBuildPhase section */ 222 | C26BAB172573B6F3008C2705 /* Sources */ = { 223 | isa = PBXSourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | C26BAB232573B6F3008C2705 /* EmployeesViewController.swift in Sources */, 227 | C26BAB412573D4A7008C2705 /* HttpRequestHelper.swift in Sources */, 228 | C26BAB392573C92B008C2705 /* Employee.swift in Sources */, 229 | C26BAB1F2573B6F3008C2705 /* AppDelegate.swift in Sources */, 230 | C26BAB3C2573C93E008C2705 /* EmployeesViewModel.swift in Sources */, 231 | C2DE3FCD2574268A00320D6C /* EmployeeCell.swift in Sources */, 232 | C26BAB452573D4FD008C2705 /* EmployeesService.swift in Sources */, 233 | C2DE3FD12574338300320D6C /* EmployeeCellViewModel.swift in Sources */, 234 | C26BAB212573B6F3008C2705 /* SceneDelegate.swift in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin PBXVariantGroup section */ 241 | C26BAB242573B6F3008C2705 /* Main.storyboard */ = { 242 | isa = PBXVariantGroup; 243 | children = ( 244 | C26BAB252573B6F3008C2705 /* Base */, 245 | ); 246 | name = Main.storyboard; 247 | sourceTree = ""; 248 | }; 249 | C26BAB292573B6F4008C2705 /* LaunchScreen.storyboard */ = { 250 | isa = PBXVariantGroup; 251 | children = ( 252 | C26BAB2A2573B6F4008C2705 /* Base */, 253 | ); 254 | name = LaunchScreen.storyboard; 255 | sourceTree = ""; 256 | }; 257 | /* End PBXVariantGroup section */ 258 | 259 | /* Begin XCBuildConfiguration section */ 260 | C26BAB2D2573B6F4008C2705 /* Debug */ = { 261 | isa = XCBuildConfiguration; 262 | buildSettings = { 263 | ALWAYS_SEARCH_USER_PATHS = NO; 264 | CLANG_ANALYZER_NONNULL = YES; 265 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 267 | CLANG_CXX_LIBRARY = "libc++"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_ENABLE_OBJC_WEAK = YES; 271 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 272 | CLANG_WARN_BOOL_CONVERSION = YES; 273 | CLANG_WARN_COMMA = YES; 274 | CLANG_WARN_CONSTANT_CONVERSION = YES; 275 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 277 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 278 | CLANG_WARN_EMPTY_BODY = YES; 279 | CLANG_WARN_ENUM_CONVERSION = YES; 280 | CLANG_WARN_INFINITE_RECURSION = YES; 281 | CLANG_WARN_INT_CONVERSION = YES; 282 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 284 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 285 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 286 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 287 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 288 | CLANG_WARN_STRICT_PROTOTYPES = YES; 289 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 290 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 291 | CLANG_WARN_UNREACHABLE_CODE = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = dwarf; 295 | ENABLE_STRICT_OBJC_MSGSEND = YES; 296 | ENABLE_TESTABILITY = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu11; 298 | GCC_DYNAMIC_NO_PIC = NO; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_OPTIMIZATION_LEVEL = 0; 301 | GCC_PREPROCESSOR_DEFINITIONS = ( 302 | "DEBUG=1", 303 | "$(inherited)", 304 | ); 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 14.2; 312 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 313 | MTL_FAST_MATH = YES; 314 | ONLY_ACTIVE_ARCH = YES; 315 | SDKROOT = iphoneos; 316 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 317 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 318 | }; 319 | name = Debug; 320 | }; 321 | C26BAB2E2573B6F4008C2705 /* Release */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | CLANG_ANALYZER_NONNULL = YES; 326 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 328 | CLANG_CXX_LIBRARY = "libc++"; 329 | CLANG_ENABLE_MODULES = YES; 330 | CLANG_ENABLE_OBJC_ARC = YES; 331 | CLANG_ENABLE_OBJC_WEAK = YES; 332 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 333 | CLANG_WARN_BOOL_CONVERSION = YES; 334 | CLANG_WARN_COMMA = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 338 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INFINITE_RECURSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 345 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 347 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 348 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 349 | CLANG_WARN_STRICT_PROTOTYPES = YES; 350 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 351 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 352 | CLANG_WARN_UNREACHABLE_CODE = YES; 353 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 354 | COPY_PHASE_STRIP = NO; 355 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 356 | ENABLE_NS_ASSERTIONS = NO; 357 | ENABLE_STRICT_OBJC_MSGSEND = YES; 358 | GCC_C_LANGUAGE_STANDARD = gnu11; 359 | GCC_NO_COMMON_BLOCKS = YES; 360 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 361 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 362 | GCC_WARN_UNDECLARED_SELECTOR = YES; 363 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 364 | GCC_WARN_UNUSED_FUNCTION = YES; 365 | GCC_WARN_UNUSED_VARIABLE = YES; 366 | IPHONEOS_DEPLOYMENT_TARGET = 14.2; 367 | MTL_ENABLE_DEBUG_INFO = NO; 368 | MTL_FAST_MATH = YES; 369 | SDKROOT = iphoneos; 370 | SWIFT_COMPILATION_MODE = wholemodule; 371 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 372 | VALIDATE_PRODUCT = YES; 373 | }; 374 | name = Release; 375 | }; 376 | C26BAB302573B6F4008C2705 /* Debug */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 380 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 381 | CODE_SIGN_STYLE = Automatic; 382 | DEVELOPMENT_TEAM = 6P8Q9Q6J62; 383 | INFOPLIST_FILE = MVVMExample/Info.plist; 384 | LD_RUNPATH_SEARCH_PATHS = ( 385 | "$(inherited)", 386 | "@executable_path/Frameworks", 387 | ); 388 | PRODUCT_BUNDLE_IDENTIFIER = com.example.MVVMExample; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | SWIFT_VERSION = 5.0; 391 | TARGETED_DEVICE_FAMILY = "1,2"; 392 | }; 393 | name = Debug; 394 | }; 395 | C26BAB312573B6F4008C2705 /* Release */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 399 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 400 | CODE_SIGN_STYLE = Automatic; 401 | DEVELOPMENT_TEAM = 6P8Q9Q6J62; 402 | INFOPLIST_FILE = MVVMExample/Info.plist; 403 | LD_RUNPATH_SEARCH_PATHS = ( 404 | "$(inherited)", 405 | "@executable_path/Frameworks", 406 | ); 407 | PRODUCT_BUNDLE_IDENTIFIER = com.example.MVVMExample; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | SWIFT_VERSION = 5.0; 410 | TARGETED_DEVICE_FAMILY = "1,2"; 411 | }; 412 | name = Release; 413 | }; 414 | /* End XCBuildConfiguration section */ 415 | 416 | /* Begin XCConfigurationList section */ 417 | C26BAB162573B6F3008C2705 /* Build configuration list for PBXProject "MVVMExample" */ = { 418 | isa = XCConfigurationList; 419 | buildConfigurations = ( 420 | C26BAB2D2573B6F4008C2705 /* Debug */, 421 | C26BAB2E2573B6F4008C2705 /* Release */, 422 | ); 423 | defaultConfigurationIsVisible = 0; 424 | defaultConfigurationName = Release; 425 | }; 426 | C26BAB2F2573B6F4008C2705 /* Build configuration list for PBXNativeTarget "MVVMExample" */ = { 427 | isa = XCConfigurationList; 428 | buildConfigurations = ( 429 | C26BAB302573B6F4008C2705 /* Debug */, 430 | C26BAB312573B6F4008C2705 /* Release */, 431 | ); 432 | defaultConfigurationIsVisible = 0; 433 | defaultConfigurationName = Release; 434 | }; 435 | /* End XCConfigurationList section */ 436 | }; 437 | rootObject = C26BAB132573B6F3008C2705 /* Project object */; 438 | } 439 | --------------------------------------------------------------------------------