Diffable datasource in check history list

This commit is contained in:
Selim Mustafaev 2020-12-18 08:51:57 +03:00
parent 9959fbc854
commit 6d0fb32fa7
6 changed files with 41 additions and 85 deletions

View File

@ -25,7 +25,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = Realm.Configuration( let config = Realm.Configuration(
schemaVersion: 27, schemaVersion: 28,
migrationBlock: { migration, oldSchemaVersion in migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion <= 3 { if oldSchemaVersion <= 3 {
var numbers: [String] = [] var numbers: [String] = []

View File

@ -18,7 +18,7 @@ extension AudioRecord: Dated {
} }
} }
extension RandomAccessCollection where Element: Dated & Equatable & Differentiable { extension RandomAccessCollection where Element: Dated & Hashable & Differentiable {
func groupedByDate() -> [DateSection<Element>] { func groupedByDate() -> [DateSection<Element>] {
let now = Date() let now = Date()
let monthStart = now.dateAtStartOf(.month) let monthStart = now.dateAtStartOf(.month)

View File

@ -2,7 +2,7 @@ import Foundation
import SwiftDate import SwiftDate
import DifferenceKit import DifferenceKit
struct DateSection<T>: Differentiable, DifferentiableSection where T: Differentiable { struct DateSection<T>: Differentiable, DifferentiableSection, Hashable where T: Differentiable & Hashable {
var timestamp: Double = 0 var timestamp: Double = 0
var header: String var header: String
@ -59,4 +59,15 @@ struct DateSection<T>: Differentiable, DifferentiableSection where T: Differenti
func isContentEqual(to source: DateSection<T>) -> Bool { func isContentEqual(to source: DateSection<T>) -> Bool {
return self.differenceIdentifier == source.differenceIdentifier return self.differenceIdentifier == source.differenceIdentifier
} }
// MARK: - Hasable
static func == (lhs: DateSection<T>, rhs: DateSection<T>) -> Bool {
return lhs.timestamp == rhs.timestamp && lhs.elements == rhs.elements
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.timestamp)
hasher.combine(self.elements)
}
} }

View File

@ -3,7 +3,7 @@ import RealmSwift
class VehicleAd: Object, Decodable { class VehicleAd: Object, Decodable {
@objc dynamic var id: Int = 0 @objc dynamic var id: Int = 0
@objc dynamic var url: String = "" @objc dynamic var url: String?
@objc dynamic var price: String? @objc dynamic var price: String?
@objc dynamic var date: TimeInterval = Date().timeIntervalSince1970 @objc dynamic var date: TimeInterval = Date().timeIntervalSince1970
@objc dynamic var mileage: String? @objc dynamic var mileage: String?

View File

@ -3,7 +3,13 @@ import RealmSwift
import DifferenceKit import DifferenceKit
import ExceptionCatcher import ExceptionCatcher
class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where Item: Object & Identifiable & Dated & Differentiable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item { class RealmDiffableDataSourse<Item>: UITableViewDiffableDataSource<DateSection<Item>, Item> where Item: Hashable & ContentEquatable & ContentIdentifiable {
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return snapshot().sectionIdentifiers[section].header
}
}
class RealmSectionedDataSource<Item,Cell> where Item: Object & Identifiable & Dated & Differentiable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item {
private var tv: UITableView private var tv: UITableView
private var data: Results<Item> private var data: Results<Item>
@ -11,97 +17,31 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where
private var sections: [DateSection<Item>] = [] private var sections: [DateSection<Item>] = []
private var cellIdentifier: String private var cellIdentifier: String
private var lastUpdateTime = Date() private var lastUpdateTime = Date()
private var dataSource: RealmDiffableDataSourse<Item>!
init(table: UITableView, data: Results<Item>, cellIdentifier: String = String(describing: Cell.self)) { init(table: UITableView, data: Results<Item>, cellIdentifier: String = String(describing: Cell.self)) {
self.tv = table self.tv = table
self.data = data self.data = data
self.cellIdentifier = cellIdentifier self.cellIdentifier = cellIdentifier
super.init()
self.tv.dataSource = self self.dataSource = RealmDiffableDataSourse<Item>(tableView: table) { tv, ip, model -> UITableViewCell? in
let cell = tv.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: ip) as? Cell
cell?.configure(with: model)
return cell
}
self.tv.dataSource = self.dataSource
self.notificationToken = self.data.observe { changes in self.notificationToken = self.data.observe { changes in
switch changes { switch changes {
case .initial: case .initial:
self.sections = self.data.groupedByDate() self.reload(animated: false)
self.tv.reloadData() case .update(_, _, _, _):
case .update(_, let deletions, let insertions, let modifications): self.reload(animated: true)
if !Calendar.current.isDateInToday(self.lastUpdateTime) {
self.sections = self.data.groupedByDate()
self.tv.reloadData()
return
}
var actions = "Deletions: \(deletions), Insertions: \(insertions), Modifications: \(modifications)"
let newSections = self.data.groupedByDate()
let changeset = StagedChangeset(source: self.sections, target: newSections, section: 0)
do {
try ExceptionCatcher.catch {
self.tv.beginUpdates()
self.tv.deleteRows(at: deletions.map(self.indexPath), with: .automatic)
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)
actions += "\n" + "Changesets count: \(changeset.count)"
if let firstChangeset = changeset.first {
actions += "\n" + "Deletions: \(firstChangeset.elementDeleted.map { $0.element })"
actions += "\n" + "Insertions: \(firstChangeset.elementInserted.map { $0.element })"
self.tv.deleteSections(IndexSet(firstChangeset.elementDeleted.map { $0.element }), with: .automatic)
self.tv.insertSections(IndexSet(firstChangeset.elementInserted.map { $0.element }), with: .automatic)
}
self.tv.endUpdates()
}
} catch {
let msg = error.localizedDescription + "\n\n" + actions
let alert = UIAlertController(title: "Error", message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.tv.window?.rootViewController?.present(alert, animated: true)
}
case .error(let err): case .error(let err):
print("Realm observer error: \(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.elements.count {
return IndexPath(row: index - sectionStartIndex, section: i)
}
sectionStartIndex += section.elements.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].elements.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].elements[indexPath.row]
cell.configure(with: item)
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sections[section].header
}
// MARK: - Public // MARK: - Public
@ -109,8 +49,13 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where
return self.sections[indexPath.section].elements[indexPath.row] return self.sections[indexPath.section].elements[indexPath.row]
} }
func reload() { func reload(animated: Bool = true) {
self.sections = self.data.groupedByDate() self.sections = self.data.groupedByDate()
self.tv.reloadData() var snapshot = NSDiffableDataSourceSnapshot<DateSection<Item>, Item>()
snapshot.appendSections(self.sections)
for section in self.sections {
snapshot.appendItems(section.elements, toSection: section)
}
self.dataSource.apply(snapshot, animatingDifferences: animated, completion: nil)
} }
} }

View File

@ -3,7 +3,7 @@ import DifferenceKit
import RxSwift import RxSwift
import RxCocoa import RxCocoa
class RxSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where Item: Dated & Equatable & Differentiable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item { class RxSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where Item: Dated & Hashable & Differentiable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item {
private var tv: UITableView private var tv: UITableView
private var cellIdentifier: String private var cellIdentifier: String
private var sections: [DateSection<Item>] = [] private var sections: [DateSection<Item>] = []