Refactoring of displaying realm results in a sectioned list

This commit is contained in:
Selim Mustafaev 2020-10-05 01:53:58 +03:00
parent cbfe729440
commit 1ab4de19e0
13 changed files with 261 additions and 161 deletions

View File

@ -88,6 +88,7 @@
7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; };
7A96AE31246B2FE400297C33 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE30246B2FE400297C33 /* Constants.swift */; };
7A96AE33246C095700297C33 /* Base64FS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE32246C095700297C33 /* Base64FS.swift */; };
7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */; };
7AAE6AD324CDDF950023860B /* VehicleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAE6AD224CDDF950023860B /* VehicleEvent.swift */; };
7AB562BA249C9E9B00473D53 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB562B9249C9E9B00473D53 /* Region.swift */; };
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
@ -102,6 +103,7 @@
7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE24C5E251F1B4E00758E39 /* Buttons.swift */; };
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */; };
7AE26A3524F31B0700625033 /* EventsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3424F31B0700625033 /* EventsController.swift */; };
7AEFC3BE2529D3CC00BADFB2 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFC3BD2529D3CC00BADFB2 /* ConfigurableCell.swift */; };
7AEFE728240455E200910EB7 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFE727240455E200910EB7 /* SettingsController.swift */; };
7AF58D2F24029C5200CE01A0 /* MagazineLayout in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D2E24029C5200CE01A0 /* MagazineLayout */; };
7AF58D3124029E1000CE01A0 /* VehicleHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF58D3024029E1000CE01A0 /* VehicleHeaderCell.swift */; };
@ -184,6 +186,7 @@
7A96AE2E246B2BCD00297C33 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
7A96AE30246B2FE400297C33 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
7A96AE32246C095700297C33 /* Base64FS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64FS.swift; sourceTree = "<group>"; };
7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxRealmDataSource.swift; sourceTree = "<group>"; };
7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = "<group>"; };
7AB562B9249C9E9B00473D53 /* Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Region.swift; sourceTree = "<group>"; };
7AB67E8B2435C38700258F61 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
@ -198,6 +201,7 @@
7AE24C5E251F1B4E00758E39 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExt.swift; sourceTree = "<group>"; };
7AE26A3424F31B0700625033 /* EventsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsController.swift; sourceTree = "<group>"; };
7AEFC3BD2529D3CC00BADFB2 /* ConfigurableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = "<group>"; };
7AEFE727240455E200910EB7 /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
7AF58D3024029E1000CE01A0 /* VehicleHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleHeaderCell.swift; sourceTree = "<group>"; };
7AF58D57240309CA00CE01A0 /* VehicleTextParamCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleTextParamCell.swift; sourceTree = "<group>"; };
@ -311,6 +315,7 @@
7A27ADF6249FEF690035F39E /* Recorder.swift */,
7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */,
7A000AA124C2EEDE001F5B00 /* Location.swift */,
7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -383,6 +388,7 @@
7A7547DF24032CB6004E8406 /* VehiclePhotoCell.swift */,
7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */,
7A813DC22508EE4F00CC93B9 /* EventCell.swift */,
7AEFC3BD2529D3CC00BADFB2 /* ConfigurableCell.swift */,
);
path = Cells;
sourceTree = "<group>";
@ -550,6 +556,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7AEFC3BE2529D3CC00BADFB2 /* ConfigurableCell.swift in Sources */,
7A96AE33246C095700297C33 /* Base64FS.swift in Sources */,
7A530B802401803A00CBFE6E /* Vehicle.swift in Sources */,
7A96AE31246B2FE400297C33 /* Constants.swift in Sources */,
@ -610,6 +617,7 @@
7A813DCB250B5DC900CC93B9 /* LocationPickerController.swift in Sources */,
7A11474423FF06CA00B424AF /* Api.swift in Sources */,
7A488C3D24A74B990054D0B2 /* RxCollectionViewRealmDataSource.swift in Sources */,
7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */,
7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */,
7A21112A24FC3D7E003BBF6F /* AudioEngine.swift in Sources */,
7A8A220B248D67B60073DFD9 /* VehicleReportImage.swift in Sources */,

View File

@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/xmartlabs/Eureka",
"state": {
"branch": null,
"revision": "9fe153d2be663621d4f53196bf1b7effb83d7017",
"version": "5.3.0"
"revision": "ae025915335f02e7277ed5446b4de002bab05866",
"version": "5.3.1"
}
},
{
@ -24,8 +24,8 @@
"repositoryURL": "https://github.com/onevcat/Kingfisher",
"state": {
"branch": null,
"revision": "22b795dd57766fa0e86773d8733efb31dc0f31fb",
"version": "5.15.1"
"revision": "2a6d1135af3915547c4b08c3b154a05e6f1075a3",
"version": "5.15.5"
}
},
{
@ -33,8 +33,8 @@
"repositoryURL": "https://github.com/airbnb/MagazineLayout",
"state": {
"branch": null,
"revision": "4a5eff2203ad8d8c7e14ea1b283b64f9320752a9",
"version": "1.6.2"
"revision": "6f88742c282de208e48cb738a7a14b7dc2651701",
"version": "1.6.3"
}
},
{
@ -42,8 +42,8 @@
"repositoryURL": "https://github.com/realm/realm-cocoa",
"state": {
"branch": null,
"revision": "f64ac045d8cb171d8e317d9b854df7215aed7466",
"version": "5.4.2"
"revision": "2dc2d259095051b997b76a07e859822661105303",
"version": "5.4.7"
}
},
{
@ -51,8 +51,8 @@
"repositoryURL": "https://github.com/realm/realm-core",
"state": {
"branch": null,
"revision": "e051fc73c56830bf3ab0b8a82f7a613968cec6c6",
"version": "6.0.26"
"revision": "2df510904ad04287926b287b4e89b786de2808c8",
"version": "6.1.3"
}
},
{

View File

@ -7,28 +7,28 @@
<key>AutoCat.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>0</integer>
</dict>
<key>Eureka (Playground) 1.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>5</integer>
<integer>3</integer>
</dict>
<key>Eureka (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>6</integer>
<integer>4</integer>
</dict>
<key>Eureka (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>4</integer>
<integer>2</integer>
</dict>
<key>GettingStarted (Playground) 1.xcscheme</key>
<dict>
@ -77,21 +77,21 @@
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>2</integer>
<integer>6</integer>
</dict>
<key>Rx (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>3</integer>
<integer>7</integer>
</dict>
<key>Rx (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>0</integer>
<integer>5</integer>
</dict>
<key>SwiftDate (Playground) 1.xcscheme</key>
<dict>
@ -133,7 +133,15 @@
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>7</integer>
<integer>1</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>7A1146FC23FDE7E500B424AF</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>

View File

@ -24,7 +24,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = Realm.Configuration(
schemaVersion: 18,
schemaVersion: 19,
migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion <= 3 {
var numbers: [String] = []
@ -44,6 +44,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
new!["isRightWheel"] = RealmOptional<Bool>(old!["isRightWheel"] as? Bool)
}
}
if oldSchemaVersion <= 18 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in
let addedDate = old!["addedDate"] as! TimeInterval
let events = old!.dynamicList("events")
new!["addedDate"] = addedDate/1000
let lastEvent = events.max { first, second in
let firstDate = first["date"] as! TimeInterval
let secondDate = second["date"] as! TimeInterval
return firstDate < secondDate
}
if let lastEvent = lastEvent {
new!["updatedDate"] = max(addedDate/1000, lastEvent["date"] as! TimeInterval)
} else {
new!["updatedDate"] = addedDate/1000
}
}
}
})
Realm.Configuration.defaultConfiguration = config

View File

@ -1,7 +1,7 @@
import UIKit
import RxSwift
class AudioRecordCell: UITableViewCell {
class AudioRecordCell: UITableViewCell, ConfigurableCell {
@IBOutlet weak var playButton: UIButton!
@IBOutlet weak var duration: UILabel!

View File

@ -0,0 +1,6 @@
import UIKit
protocol ConfigurableCell {
associatedtype Item
func configure(with item: Item)
}

View File

@ -1,6 +1,6 @@
import UIKit
class VehicleCell: UITableViewCell {
class VehicleCell: UITableViewCell, ConfigurableCell {
@IBOutlet weak var name: UILabel!
@IBOutlet weak var plate: PlateView!
@ -25,7 +25,7 @@ class VehicleCell: UITableViewCell {
} else {
self.plate.foreground = nil
}
self.date.text = formatter.string(from: Date(timeIntervalSince1970: vehicle.addedDate/1000))
self.date.text = formatter.string(from: Date(timeIntervalSince1970: vehicle.addedDate))
}
}

View File

@ -3,7 +3,6 @@ import RealmSwift
import RxSwift
import SwiftDate
import RxRealm
import RxDataSources
enum EventAction {
case doNotSend
@ -18,6 +17,7 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
@IBOutlet weak var history: UITableView!
let bag = DisposeBag()
var historyDataSource: RealmSectionedDataSource<Vehicle, VehicleCell>!
// MARK: - Lifecycle
@ -33,32 +33,11 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
self.number.inputView = keyboard
self.check.isEnabled = false
let ds = RxTableViewSectionedAnimatedDataSource<DateSection<Vehicle>>(configureCell: { dataSource, tableView, indexPath, item in
if let cell = tableView.dequeueReusableCell(withIdentifier: "VehicleCell", for: indexPath) as? VehicleCell {
cell.configure(with: item)
return cell
} else {
return UITableViewCell()
}
}, canEditRowAtIndexPath: { _, _ in true })
ds.titleForHeaderInSection = { dataSourse, index in
return dataSourse.sectionModels[index].header
}
self.history.rx.modelSelected(Vehicle.self)
.subscribe(onNext: self.updateDetailController(with:))
.disposed(by: self.bag)
DispatchQueue.main.async {
Observable.collection(from: realm.objects(Vehicle.self)
.sorted(byKeyPath: "addedDate", ascending: false))
.map { $0.groupedByDate() }
.bind(to: self.history.rx.items(dataSource: ds))
.disposed(by: self.bag)
self.historyDataSource = RealmSectionedDataSource(table: self.history, data: realm.objects(Vehicle.self).sorted(byKeyPath: "updatedDate", ascending: false))
}
self.history.rx.setDelegate(self).disposed(by: self.bag)
self.history.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
@ -94,7 +73,7 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
if let event = event {
action = .sendSpecific(event)
}
self.check(number: number, action: action)
self.check(number: number, action: action).subscribe().disposed(by: self.bag)
break
case .addVoiceRecord:
self.tabBarController?.selectedIndex = 1
@ -116,83 +95,19 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
guard let number = self.number.text else { return }
let numberNormalized = number.filter { !$0.isWhitespace }.uppercased()
self.check(number: numberNormalized, action: .receiveAndSend)
}
func check(number: String, action: EventAction) {
self.number.resignFirstResponder()
self.number.text = nil
self.check.isEnabled = false
IHProgressHUD.show()
Api.checkVehicle(by: number)
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { vehicle in
self.onReceivedVehicle(vehicle, action: action)
}, onError: { err in
if let realm = try? Realm() {
let vehicle = Vehicle(number)
try? realm.write {
realm.add(vehicle, update: .all)
}
var eventSingle = self.getEvent()
if case .sendSpecific(let event) = action {
eventSingle = Single.just(event)
}
eventSingle
.flatMap { event in event.findAddress().map{ [event] }.catchErrorJustReturn([event]) }
.catchErrorJustReturn([])
.subscribe(onSuccess: { events in
try? realm.write {
vehicle.events.append(objectsIn: events)
}
})
.disposed(by: self.bag)
}
IHProgressHUD.showError(withStatus: err.localizedDescription)
print(err.localizedDescription)
}).disposed(by: self.bag)
}
func save(vehicle: Vehicle) {
if let realm = try? Realm() {
try? realm.write {
realm.add(vehicle, update: .all)
}
self.check(number: numberNormalized, action: .receiveAndSend).subscribe { vehicle in
self.updateDetailController(with: vehicle)
IHProgressHUD.dismiss()
} onError: { error in
IHProgressHUD.show(error: error)
}
}
.disposed(by: self.bag)
func getEvent() -> Single<VehicleEvent> {
if let event = LocationManager.lastEvent, (Date().timeIntervalSince1970 - event.date) < 30 {
print("Using last event")
return Single<VehicleEvent>.just(event)
} else {
print("requesting new event")
return LocationManager.requestCurrentLocation()
}
}
func onReceivedVehicle(_ vehicle: Vehicle, action: EventAction = .receiveAndSend) {
self.save(vehicle: vehicle)
if case .doNotSend = action {
// Just do nothing
} else {
var eventSingle = self.getEvent()
if case .sendSpecific(let event) = action {
eventSingle = Single.just(event)
}
eventSingle
.flatMap { event in event.findAddress().map{ event }.catchErrorJustReturn(event) }
.flatMap {
Api.add(event: $0, to: vehicle.getNumber())
}
.subscribe(onSuccess: self.save(vehicle:), onError: { print("Error adding event: \($0)") })
.disposed(by: self.bag)
}
self.updateDetailController(with: vehicle)
IHProgressHUD.dismiss()
}
func updateDetailController(with vehicle: Vehicle) {
@ -239,27 +154,27 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
}
}
// MARK: -
// MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
guard let vehicle: Vehicle = try? self.history.rx.model(at: indexPath) else { return nil }
let vehicle = self.historyDataSource.item(at: indexPath)
let updateAction = UIContextualAction(style: .normal, title: "Update") { action, view, completion in
IHProgressHUD.show()
var eventAction: EventAction = .doNotSend
if vehicle.unrecognized, let savedEvent = vehicle.events.first {
eventAction = .sendSpecific(savedEvent.freeze())
eventAction = .sendSpecific(savedEvent)
}
Api.checkVehicle(by: vehicle.getNumber(), force: true)
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { vehicle in
self.onReceivedVehicle(vehicle, action: eventAction)
}, onError: { err in
IHProgressHUD.showError(withStatus: err.localizedDescription)
print(err.localizedDescription)
}).disposed(by: self.bag)
self.check(number: vehicle.getNumber(), action: eventAction, force: true).subscribe { vehicle in
self.updateDetailController(with: vehicle)
IHProgressHUD.dismiss()
} onError: { error in
IHProgressHUD.show(error: error)
}
.disposed(by: self.bag)
completion(true)
}
updateAction.image = UIImage(systemName: "arrow.2.circlepath")
@ -282,4 +197,71 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
configuration.performsFirstActionWithFullSwipe = false
return configuration
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vehicle = self.historyDataSource.item(at: indexPath)
self.updateDetailController(with: vehicle)
}
// MARK: - Checking number
func save(vehicle: Vehicle) throws {
let realm = try Realm()
try realm.write {
print("===== Save vehicle: \(vehicle.getNumber())")
realm.add(vehicle, update: .all)
}
}
func getEvent(for action: EventAction) -> Single<VehicleEvent> {
if case .sendSpecific(let event) = action {
return Single.just(event)
}
if let event = LocationManager.lastEvent, (Date().timeIntervalSince1970 - event.date) < 30 {
return Single<VehicleEvent>.just(event)
} else {
return LocationManager.requestCurrentLocation()
}
}
func check(number: String, action: EventAction, force: Bool = false) -> Single<Vehicle> {
let checkSingle = Api.checkVehicle(by: number, force: force)
.observeOn(MainScheduler.instance)
.map { (vehicle: Vehicle) -> Vehicle in
try self.save(vehicle: vehicle)
return vehicle
}
.do (onError: { err in
let realm = try Realm()
if realm.object(ofType: Vehicle.self, forPrimaryKey: number) == nil {
let vehicle = Vehicle(number)
try realm.write { realm.add(vehicle, update: .all) }
self.getEvent(for: action)
.flatMap { event in event.findAddress().map{ [event] }.catchErrorJustReturn([event]) }
.catchErrorJustReturn([])
.map { events in
try realm.write { vehicle.events.append(objectsIn: events) }
}
.subscribe()
.disposed(by: self.bag)
}
})
if case .doNotSend = action {
return checkSingle
} else {
return checkSingle
.flatMap { _ in self.getEvent(for: action) }
.flatMap { event in event.findAddress().map{ event }.catchErrorJustReturn(event) }
.flatMap { Api.add(event: $0, to: number) }
.observeOn(MainScheduler.instance)
.map {
try self.save(vehicle: $0)
return $0
}
}
}
}

View File

@ -3,7 +3,6 @@ import AVFoundation
import RealmSwift
import RxSwift
import RxRealm
import RxDataSources
import Intents
import CoreSpotlight
import MobileCoreServices
@ -18,6 +17,7 @@ class RecordsController: UIViewController, UITableViewDelegate {
let bag = DisposeBag()
var recordDisposable: Disposable?
var audioSessionObserver: NSObjectProtocol?
var recordsDataSource: RealmSectionedDataSource<AudioRecord, AudioRecordCell>!
let validLetters = Constants.pnLettersMap.keys.map(String.init)
@ -31,28 +31,11 @@ class RecordsController: UIViewController, UITableViewDelegate {
self.recorder = Recorder()
let ds = RxTableViewSectionedAnimatedDataSource<DateSection<AudioRecord>>(configureCell: { dataSource, tableView, indexPath, item in
if let cell = tableView.dequeueReusableCell(withIdentifier: "AudioRecordCell", for: indexPath) as? AudioRecordCell {
cell.configure(with: item.freeze())
return cell
} else {
return UITableViewCell()
}
}, canEditRowAtIndexPath: { _, _ in true })
ds.titleForHeaderInSection = { dataSourse, index in
return dataSourse.sectionModels[index].header
}
DispatchQueue.main.async {
Observable.collection(from: realm.objects(AudioRecord.self)
.sorted(byKeyPath: "addedDate", ascending: false))
.map { $0.groupedByDate() }
.bind(to: self.tableView.rx.items(dataSource: ds))
.disposed(by: self.bag)
self.recordsDataSource = RealmSectionedDataSource(table: self.tableView, data: realm.objects(AudioRecord.self)
.sorted(byKeyPath: "addedDate", ascending: false))
}
self.tableView.rx.setDelegate(self).disposed(by: self.bag)
self.tableView.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
@ -241,9 +224,10 @@ class RecordsController: UIViewController, UITableViewDelegate {
// MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
guard let record: AudioRecord = try? self.tableView.rx.model(at: indexPath) else { return nil }
guard let cell = tableView.cellForRow(at: indexPath) else { return nil }
let record = self.recordsDataSource.item(at: indexPath)
let check = UIContextualAction(style: .normal, title: "Check") { action, view, completion in
if let number = record.number {
self.check(number: number, event: record.event)
@ -339,7 +323,7 @@ class RecordsController: UIViewController, UITableViewDelegate {
alert.dismiss(animated: true)
}))
alert.addTextField { tf in
tf.text = record.number ?? record.rawText
tf.text = record.number ?? record.rawText.replacingOccurrences(of: " ", with: "")
NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: tf, queue: OperationQueue.main) { _ in
done.isEnabled = self.valid(number: tf.text?.uppercased() ?? "")
}

View File

@ -8,12 +8,7 @@ protocol Dated {
extension Vehicle: Dated {
var date: Date {
if let lastEventDate = self.events.max(by: { $0.date < $1.date })?.date {
let ts = max(self.addedDate/1000, lastEventDate)
return Date(timeIntervalSince1970: ts)
} else {
return Date(timeIntervalSince1970: self.addedDate/1000)
}
return Date(timeIntervalSince1970: self.updatedDate)
}
}

View File

@ -2,7 +2,7 @@ import Foundation
import RxDataSources
import SwiftDate
struct DateSection<T>: AnimatableSectionModelType where T: IdentifiableType, T: Equatable {
struct DateSection<T>: AnimatableSectionModelType, Equatable where T: IdentifiableType, T: Equatable {
var timestamp: Double = 0
var header: String

View File

@ -82,6 +82,7 @@ class Vehicle: Object, Decodable, IdentifiableType {
var isRightWheel = RealmOptional<Bool>()
@objc dynamic var isJapanese: Bool = false
@objc dynamic var addedDate: TimeInterval = 0
@objc dynamic var updatedDate: TimeInterval = 0
@objc dynamic var addedBy: String = ""
let photos = List<VehiclePhoto>()
let ownershipPeriods = List<VehicleOwnershipPeriod>()
@ -133,7 +134,7 @@ class Vehicle: Object, Decodable, IdentifiableType {
self.pts = try container.decodeIfPresent(String.self, forKey: .pts)
self.isRightWheel = try container.decode(RealmOptional<Bool>.self, forKey: .isRightWheel)
self.isJapanese = try container.decode(Bool.self, forKey: .isJapanese)
self.addedDate = try container.decode(TimeInterval.self, forKey: .addedDate)
self.addedDate = (try container.decode(TimeInterval.self, forKey: .addedDate))/1000
self.addedBy = try container.decode(String.self, forKey: .addedBy)
if let photosArray = try container.decodeIfPresent([VehiclePhoto].self, forKey: .photos) {
@ -148,6 +149,12 @@ class Vehicle: Object, Decodable, IdentifiableType {
self.events.append(objectsIn: eventsArray)
}
if let lastEventDate = self.events.max(by: { $0.date < $1.date })?.date {
self.updatedDate = max(self.addedDate, lastEventDate)
} else {
self.updatedDate = self.addedDate
}
self.identifier = self.number
}
@ -159,7 +166,8 @@ class Vehicle: Object, Decodable, IdentifiableType {
init(_ number: String) {
self.identifier = number
self.number = number
self.addedDate = Date().timeIntervalSince1970*1000
self.addedDate = Date().timeIntervalSince1970
self.updatedDate = self.addedDate
}
func getNumber() -> String {

View File

@ -0,0 +1,89 @@
import UIKit
import RealmSwift
import RxDataSources
class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where Item: Object & IdentifiableType & Dated, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item {
private var tv: UITableView
private var data: Results<Item>
private var notificationToken: NotificationToken?
private var sections: [DateSection<Item>] = []
private var cellIdentifier: String
init(table: UITableView, data: Results<Item>, cellIdentifier: String = String(describing: Cell.self)) {
self.tv = table
self.data = data
self.cellIdentifier = cellIdentifier
super.init()
self.tv.dataSource = self
self.notificationToken = self.data.observe { changes in
switch changes {
case .initial:
self.sections = self.data.groupedByDate()
self.tv.reloadData()
case .update(_, let deletions, let insertions, let modifications):
print("Deletions: \(deletions.count), Insertions: \(insertions.count), Modifications: \(modifications.count)")
let newSections = self.data.groupedByDate()
let diff = newSections.difference(from: self.sections)
self.tv.beginUpdates()
let delPaths = deletions.map(self.indexPath)
self.tv.deleteRows(at: delPaths, with: .automatic)
//diff.filter { if case .remove = $0 }
self.sections = self.data.groupedByDate()
self.tv.insertRows(at: insertions.map(self.indexPath), with: .automatic)
self.tv.reloadRows(at: modifications.map(self.indexPath), with: .automatic)
self.tv.endUpdates()
case .error(let err):
print("Realm observer error: \(err)")
}
}
}
// Here I assume that sections keep order of elements the same as in initial flat collection
// Otherwise it will not work properly
func indexPath(by index: Int) -> IndexPath {
var sectionStartIndex = 0
for (i, section) in self.sections.enumerated() {
if index < sectionStartIndex + section.items.count {
print("Index \(index) -> (\(i), \(index - sectionStartIndex))")
return IndexPath(row: index - sectionStartIndex, section: i)
}
sectionStartIndex += section.items.count
}
return IndexPath(row: 0, section: 0)
}
// MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
return self.sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sections[section].items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath) as? Cell else {
return UITableViewCell()
}
let item = self.sections[indexPath.section].items[indexPath.row]
cell.configure(with: item)
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sections[section].header
}
// MARK: - Public
func item(at indexPath: IndexPath) -> Item {
return self.sections[indexPath.section].items[indexPath.row]
}
}