Compare commits
5 Commits
b9bd3ae310
...
146c90f2ee
| Author | SHA1 | Date | |
|---|---|---|---|
| 146c90f2ee | |||
| 7501ce5936 | |||
| 3238defe84 | |||
| fa5f782968 | |||
| 6280aa42cb |
@ -27,7 +27,7 @@ public enum EmployeeType: String, Codable, CustomStringConvertible {
|
||||
/// Employee Object
|
||||
/// JSON Object defintion
|
||||
/// - https://square.github.io/microsite/mobile-interview-project/
|
||||
public struct Employee: Codable {
|
||||
public struct Employee: Hashable, Codable {
|
||||
|
||||
/// The unique identifier for the employee. Represented as a UUID.
|
||||
public let uuid: UUID
|
||||
|
||||
@ -34,24 +34,46 @@ class EmployeesViewController: UIViewController {
|
||||
// Prevents multiple calls during rapid scrolling
|
||||
private var isFetchingNextPage: Bool = false
|
||||
|
||||
private var dataSource: UITableViewDiffableDataSource<Int, Employee>!
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupUI()
|
||||
setupDataSource()
|
||||
bindViewModel()
|
||||
viewModel.fetchEmployees()
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
|
||||
/// Setting up the TableView Datasource
|
||||
private func setupDataSource() {
|
||||
dataSource = UITableViewDiffableDataSource<Int, Employee>(tableView: tableView) { tableView, indexPath, employee in
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: EmployeeTableViewCell.identifier,for: indexPath) as? EmployeeTableViewCell else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
cell.configure(with: EmployeeCellViewModel(employee: employee))
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
//Snapshot Handling
|
||||
private func applySnapshot(employees: [Employee]) {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Int, Employee>()
|
||||
snapshot.appendSections([0])
|
||||
snapshot.appendItems(employees, toSection: 0)
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
|
||||
/// Setup the UI by adding the views to the main view
|
||||
private func setupUI() {
|
||||
view.backgroundColor = .white
|
||||
|
||||
// Configure TableView
|
||||
tableView.register(EmployeeTableViewCell.self, forCellReuseIdentifier: EmployeeTableViewCell.identifier)
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
view.addSubview(tableView)
|
||||
tableView.frame = view.bounds
|
||||
@ -109,9 +131,8 @@ class EmployeesViewController: UIViewController {
|
||||
private func bindViewModel() {
|
||||
viewModel.$employees
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.updateFooter()
|
||||
self?.tableView.reloadData()
|
||||
.sink { [weak self] employees in
|
||||
self?.applySnapshot(employees: employees)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@ -135,6 +156,26 @@ class EmployeesViewController: UIViewController {
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func animateEmployeeChanges(from oldEmployees: [Employee], to newEmployees: [Employee]) {
|
||||
let oldCount = oldEmployees.count
|
||||
let newCount = newEmployees.count
|
||||
|
||||
// Case: Removing all employees
|
||||
if oldCount > 0 && newCount == 0 {
|
||||
let indexPaths = (0..<oldCount).map { IndexPath(row: $0, section: 0) }
|
||||
tableView.performBatchUpdates {
|
||||
tableView.deleteRows(at: indexPaths, with: .fade)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Case: Resetting with new data (or switching modes)
|
||||
if newCount > 0 {
|
||||
tableView.reloadData()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/// Show state in specific use-cases for the EmployeesViewModel
|
||||
private func updateFooter() {
|
||||
var footerMessage: String?
|
||||
@ -212,19 +253,3 @@ extension EmployeesViewController {
|
||||
viewModel.changeMode(to: selectedMode)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark: - UITableViewDataSource
|
||||
extension EmployeesViewController: UITableViewDataSource {
|
||||
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return viewModel.employees.count
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: EmployeeTableViewCell.identifier, for: indexPath) as? EmployeeTableViewCell else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
let employee = viewModel.employees[indexPath.row]
|
||||
cell.configure(with: EmployeeCellViewModel(employee: employee))
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import Combine
|
||||
@MainActor
|
||||
public class EmployeesViewModel: ObservableObject {
|
||||
private var serviceMode: EmployeeServiceMode = .production
|
||||
private var employeeService: EmployeeServiceProtocol = EmployeeService.shared
|
||||
private var employeeService: EmployeeServiceProtocol = MockEmployeeService.shared
|
||||
@Published public private(set) var employees: [Employee] = []
|
||||
@Published public private(set) var errorMessage: String? = nil
|
||||
@Published public private(set) var isLoading: Bool = false
|
||||
@ -34,6 +34,7 @@ public class EmployeesViewModel: ObservableObject {
|
||||
/// Observe changes to sortField and sortOrder and debounce fetch calls
|
||||
private func observeSortingChanges() {
|
||||
Publishers.CombineLatest($sortField, $sortOrder)
|
||||
.dropFirst()
|
||||
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
||||
.sink { [weak self] _, _ in
|
||||
self?.resetAndFetchEmployees()
|
||||
@ -55,16 +56,16 @@ public class EmployeesViewModel: ObservableObject {
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder)
|
||||
|
||||
totalEmployees = wrapper.total
|
||||
currentPage = page
|
||||
hasMorePages = wrapper.employees.count < totalEmployees
|
||||
|
||||
// Update published properties
|
||||
if page == 1 {
|
||||
employees = wrapper.employees // Replace list for the first page
|
||||
} else {
|
||||
employees.append(contentsOf: wrapper.employees) // Append for subsequent pages
|
||||
}
|
||||
|
||||
totalEmployees = wrapper.total
|
||||
currentPage = page
|
||||
hasMorePages = employees.count < totalEmployees
|
||||
} catch {
|
||||
// Handle errors
|
||||
errorMessage = "An unexpected error occurred, please try to refresh."
|
||||
|
||||
Loading…
Reference in New Issue
Block a user