deep-linking-sample/Apps/Locations/Sources/Screens/LocationsList/LocationsListViewController.swift
Javier Cicchelli c8d2c288af [Feature] Locations list (#10)
This PR contains the work done to fetch a set of locations from a remote server to then persist them into the persistence stack and finally to display these data into the locations list screen.

To give further details about the work done:
- [x] implemented the `LoadingSpinnerView` and `ErrorMessageView` custom views;
- [x] implemented the outlets of the `LocationsListViewController` view controller;
- [x] add properties and functions to the `LocationsListViewModeling` protocol to support reactive updates, load data, and table data source conformances;
- [x] deactivated the Location entity code generation from the Core Data model in the `Persistence` library;
- [x] add fetch requests builder functions to the `NSFetchRequest+Location` extension in the `Persistence` library;
- [x] implemented the `LoadRemoteLocationUseCase` use case;
- [x] implemented the loading of locations in the LocationsListViewModel view model;
- [x] implemented properties and functions in the LocationsListViewModel view model to support the table data source conformance of the `LocationsListViewController` view controller;
- [x] implemented the `LocationViewCell` custom cell;
- [x] registered the `LocationViewCell` with the table of the `LocationsListViewController` view controller and implemented its update with real data from Location entities.

Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Reviewed-on: rock-n-code/deep-linking-assignment#10
2023-04-12 16:58:27 +00:00

171 lines
4.5 KiB
Swift

//
// LocationsListViewController.swift
// Locations
//
// Created by Javier Cicchelli on 08/04/2023.
// Copyright © 2023 Röck+Cöde. All rights reserved.
//
import Combine
import Core
import UIKit
class LocationsListViewController: BaseViewController {
// MARK: Properties
private let viewModel: LocationsListViewModeling
private var cancellables: Set<AnyCancellable> = []
// MARK: Outlets
private lazy var error = ErrorMessageView()
private lazy var loading = LoadingSpinnerView()
private lazy var table = {
let table = UITableView(frame: .zero, style: .plain)
table.dataSource = self
table.delegate = self
table.translatesAutoresizingMaskIntoConstraints = false
table.register(LocationViewCell.self, forCellReuseIdentifier: LocationViewCell.identifier)
return table
}()
// MARK: Initialisers
init(viewModel: LocationsListViewModeling) {
self.viewModel = viewModel
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
setupBar()
setupView()
bindViewModel()
viewModel.loadLocations()
}
}
// MARK: - UITableViewDataSource
extension LocationsListViewController: UITableViewDataSource {
// MARK: Functions
func numberOfSections(in tableView: UITableView) -> Int {
viewModel.numberOfSectionsInData
}
func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
viewModel.numberOfDataItems(in: section)
}
func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(
withIdentifier: LocationViewCell.identifier,
for: indexPath
) as? LocationViewCell else {
return .init()
}
let entity = viewModel.dataItem(at: indexPath)
cell.update(
iconName: entity.source == .remote ? "network" : "house",
name: entity.name,
latitude: entity.latitude,
longitude: entity.longitude
)
return cell
}
}
// MARK: - UITableViewDelegate
extension LocationsListViewController: UITableViewDelegate {}
// MARK: - Helpers
private extension LocationsListViewController {
// MARK: Functions
func setupBar() {
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.tintColor = .red
navigationItem.rightBarButtonItem = .init(
title: "Add",
style: .plain,
target: self,
action: #selector(addLocationPressed)
)
title = "Locations"
}
func setupView() {
view.addSubview(table)
view.addSubview(error)
view.addSubview(loading)
error.onRetry = {
self.viewModel.loadLocations()
}
NSLayoutConstraint.activate([
error.widthAnchor.constraint(equalToConstant: 300),
view.centerXAnchor.constraint(equalTo: error.centerXAnchor),
view.centerYAnchor.constraint(equalTo: error.centerYAnchor),
view.centerXAnchor.constraint(equalTo: loading.centerXAnchor),
view.centerYAnchor.constraint(equalTo: loading.centerYAnchor),
view.bottomAnchor.constraint(equalTo: table.bottomAnchor),
view.leadingAnchor.constraint(equalTo: table.leadingAnchor),
view.topAnchor.constraint(equalTo: table.topAnchor),
view.trailingAnchor.constraint(equalTo: table.trailingAnchor),
])
}
func bindViewModel() {
viewModel
.viewStatusPublisher
.receive(on: RunLoop.main)
.sink { viewStatus in
self.navigationItem.rightBarButtonItem?.isEnabled = viewStatus == .loaded
self.error.isHidden = viewStatus != .error
self.loading.isHidden = viewStatus != .loading
self.table.isHidden = viewStatus != .loaded
if viewStatus == .loaded {
self.table.reloadData()
}
}
.store(in: &cancellables)
}
@objc func addLocationPressed() {
viewModel.openAddLocation()
}
}