import UIKit import MapKit import RxSwift import Realm import RealmSwift class EventPin: NSObject, MKAnnotation { var coordinate: CLLocationCoordinate2D var title: String? var subtitle: String? init(coordinate: CLLocationCoordinate2D, title: String?, subtitle: String) { self.coordinate = coordinate self.title = title self.subtitle = subtitle } } enum EventsMode { case map case list } class EventsController: UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet weak var map: MKMapView! @IBOutlet weak var tableView: UITableView! let bag = DisposeBag() var modeButton: UIBarButtonItem! var addButton: UIBarButtonItem! var mode: EventsMode = .map public var vehicle: Vehicle? { didSet { if let vehicle = self.vehicle { self.pins = vehicle.events.map { event in let coordinate = CLLocationCoordinate2D(latitude: event.latitude, longitude: event.longitude) let subtitle = event.address ?? "\(event.latitude), \(event.longitude)" let date = Date(timeIntervalSince1970: event.date) let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .short let title = formatter.string(from: date) return EventPin(coordinate: coordinate, title: title, subtitle: subtitle) } } else { } if self.isViewLoaded { self.updateInterface() } } } private var pins: [EventPin] = [] override func viewDidLoad() { super.viewDidLoad() self.title = self.vehicle?.number ?? "Events" #if targetEnvironment(macCatalyst) self.map.showsZoomControls = true #endif self.modeButton = UIBarButtonItem(image: UIImage(systemName: "list.bullet"), style: .plain, target: self, action: #selector(switchMode(_:))) self.addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addEvent(_:))) self.navigationItem.rightBarButtonItems = [self.modeButton, self.addButton] self.updateInterface() } func updateInterface() { self.map.removeAnnotations(self.map.annotations) self.map.addAnnotations(self.pins) self.centerMap() self.tableView.reloadData() } func centerMap() { guard let vehicle = self.vehicle else { return } var minLat = 0.0, maxLat = 0.0, minLon = 0.0, maxLon = 0.0 for event in vehicle.events { if event.latitude < minLat || minLat == 0 { minLat = event.latitude } if event.latitude > maxLat || maxLat == 0 { maxLat = event.latitude } if event.longitude < minLon || minLon == 0 { minLon = event.longitude } if event.longitude > maxLon || maxLon == 0 { maxLon = event.longitude } } let center = CLLocationCoordinate2D(latitude: (minLat + maxLat)/2, longitude: (minLon + maxLon)/2) let leftTop = CLLocation(latitude: minLat, longitude: minLon) let rightBottom = CLLocation(latitude: maxLat, longitude: maxLon) var diagonal = leftTop.distance(from: rightBottom)*1.2 if diagonal < 1000 { diagonal = 1000 } let region = MKCoordinateRegion(center: center, latitudinalMeters: diagonal, longitudinalMeters: diagonal) self.map.setRegion(region, animated: true) } @objc func switchMode(_ sender: UIBarButtonItem) { switch self.mode { case .map: self.mode = .list self.tableView.reloadData() self.tableView.isHidden = false self.map.isHidden = true self.modeButton.image = UIImage(systemName: "map") case .list: self.mode = .map self.tableView.isHidden = true self.map.isHidden = false self.modeButton.image = UIImage(systemName: "list.bullet") } } // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.vehicle?.events.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "EventCell", for: indexPath) as? EventCell else { return UITableViewCell() } if let event = self.vehicle?.events[indexPath.row] { cell.configure(with: event) } return cell } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let delete = UIContextualAction(style: .destructive, title: "Delete") { action, view, completion in self.deleteEvent(index: indexPath.row, completion: completion) } delete.image = UIImage(systemName: "trash") let edit = UIContextualAction(style: .normal, title: "Edit") { action, view, completion in self.editEvent(index: indexPath.row) completion(true) } edit.image = UIImage(systemName: "pencil") edit.backgroundColor = .systemBlue let configuration = UISwipeActionsConfiguration(actions: [delete, edit]) configuration.performsFirstActionWithFullSwipe = false return configuration } // MARK: - Event actions func deleteEvent(index: Int, completion: @escaping (Bool) -> Void) { guard let vehicle = self.vehicle else { IHProgressHUD.showError(withStatus: "Unknown vehicle") return } let event = vehicle.events[index] if let eventId = event.id { IHProgressHUD.show() Api.remove(event: eventId).observeOn(MainScheduler.instance).subscribe(onSuccess: { vehicle in completion(self.update(vehicle: vehicle)) }, onError: { error in completion(false) IHProgressHUD.show(error: error) print(error) }).disposed(by: self.bag) } else { self.showAlert(title: "Error", message: "Event ID is not found. Please try to update vehicle record, containing this event.") } } func editEvent(index: Int) { guard let vehicle = self.vehicle else { IHProgressHUD.showError(withStatus: "Unknown vehicle") return } let event = vehicle.events[index] let sb = UIStoryboard(name: "Main", bundle: nil) let controller = sb.instantiateViewController(identifier: "LocationEditController") as LocationEditController controller.title = "Edit event" controller.date = Date(timeIntervalSince1970: event.date) controller.placemark = Placemark(latitude: event.latitude, longitude: event.longitude, address: event.address) controller.onDone = { newEvent in newEvent.id = event.id self.navigationController?.popViewController(animated: true, completion: { IHProgressHUD.show() Api.edit(event: newEvent) .observeOn(MainScheduler.instance) .subscribe(onSuccess: { self.update(vehicle: $0) }, onError: { error in IHProgressHUD.show(error: error) }) .disposed(by: self.bag) }) } self.navigationController?.pushViewController(controller, animated: true) } @objc func addEvent(_ sender: UIBarButtonItem) { guard let vehicle = self.vehicle else { IHProgressHUD.showError(withStatus: "Unknown vehicle") return } let sb = UIStoryboard(name: "Main", bundle: nil) let controller = sb.instantiateViewController(identifier: "LocationEditController") as LocationEditController controller.title = "Add new event" controller.onDone = { newEvent in self.navigationController?.popViewController(animated: true, completion: { IHProgressHUD.show() Api.add(event: newEvent, to: vehicle.number) .observeOn(MainScheduler.instance) .subscribe(onSuccess: { self.update(vehicle: $0) }, onError: { error in IHProgressHUD.show(error: error) }) .disposed(by: self.bag) }) } self.navigationController?.pushViewController(controller, animated: true) } @discardableResult func update(vehicle: Vehicle) -> Bool { do { if let realm = self.vehicle?.realm { try realm.write { realm.add(vehicle, update: .all) } } self.vehicle = vehicle IHProgressHUD.dismiss() return true } catch { IHProgressHUD.show(error: error) print(error) return false } } }