From 242ad77e5e2769433dca21baff7985c9cf352b0d Mon Sep 17 00:00:00 2001 From: Vasyl Myronchuk Date: Fri, 2 Feb 2018 15:22:02 +0200 Subject: [PATCH 1/6] Added SectionedViewDataSourceType conformance to table/collection view delegates. It eliminates the need to manually reimplement functionality already present in RxCocoa (like modelSelected, modelDeselected, etc). --- .../CollectionViewController.swift | 2 +- .../RxRealmDataSources/ViewController.swift | 2 +- Pod/Classes/Reactive+RxRealmDataSources.swift | 27 ------------------- .../RxCollectionViewRealmDataSource.swift | 4 +-- Pod/Classes/RxTableViewRealmDataSource.swift | 4 +-- 5 files changed, 6 insertions(+), 33 deletions(-) diff --git a/Example/RxRealmDataSources/CollectionViewController.swift b/Example/RxRealmDataSources/CollectionViewController.swift index 579c0d4..b787bad 100644 --- a/Example/RxRealmDataSources/CollectionViewController.swift +++ b/Example/RxRealmDataSources/CollectionViewController.swift @@ -48,7 +48,7 @@ class CollectionViewController: UIViewController { .disposed(by: bag) // react on cell taps - collectionView.rx.realmModelSelected(Lap.self) + collectionView.rx.modelSelected(Lap.self) .map({ $0.text }) .bind(to: rx.title) .disposed(by: bag) diff --git a/Example/RxRealmDataSources/ViewController.swift b/Example/RxRealmDataSources/ViewController.swift index f7b7c8e..f31ad36 100644 --- a/Example/RxRealmDataSources/ViewController.swift +++ b/Example/RxRealmDataSources/ViewController.swift @@ -48,7 +48,7 @@ class ViewController: UIViewController { .disposed(by: bag) // react on cell taps - tableView.rx.realmModelSelected(Lap.self) + tableView.rx.modelSelected(Lap.self) .map({ $0.text }) .bind(to: rx.title) .disposed(by: bag) diff --git a/Pod/Classes/Reactive+RxRealmDataSources.swift b/Pod/Classes/Reactive+RxRealmDataSources.swift index c51ca48..1093289 100644 --- a/Pod/Classes/Reactive+RxRealmDataSources.swift +++ b/Pod/Classes/Reactive+RxRealmDataSources.swift @@ -29,20 +29,6 @@ extension Reactive where Base: UITableView { ds.applyChanges(items: AnyRealmCollection(results), changes: changes) } } - - public func realmModelSelected(_ modelType: E.Type) -> ControlEvent where E: RealmSwift.Object { - - let source: Observable = self.itemSelected.flatMap { [weak view = self.base as UITableView] indexPath -> Observable in - guard let view = view, let ds = view.dataSource as? RxTableViewRealmDataSource else { - return Observable.empty() - } - - return Observable.just(ds.model(at: indexPath)) - } - - return ControlEvent(events: source) - } - } extension Reactive where Base: UICollectionView { @@ -58,19 +44,6 @@ extension Reactive where Base: UICollectionView { ds.applyChanges(items: AnyRealmCollection(results), changes: changes) } } - - public func realmModelSelected(_ modelType: E.Type) -> ControlEvent where E: RealmSwift.Object { - - let source: Observable = self.itemSelected.flatMap { [weak view = self.base as UICollectionView] indexPath -> Observable in - guard let view = view, let ds = view.dataSource as? RxCollectionViewRealmDataSource else { - return Observable.empty() - } - - return Observable.just(ds.model(at: indexPath)) - } - - return ControlEvent(events: source) - } } diff --git a/Pod/Classes/RxCollectionViewRealmDataSource.swift b/Pod/Classes/RxCollectionViewRealmDataSource.swift index 8883732..e6d5f80 100644 --- a/Pod/Classes/RxCollectionViewRealmDataSource.swift +++ b/Pod/Classes/RxCollectionViewRealmDataSource.swift @@ -20,7 +20,7 @@ import RxRealm public typealias CollectionCellFactory = (RxCollectionViewRealmDataSource, UICollectionView, IndexPath, E) -> UICollectionViewCell public typealias CollectionCellConfig = (CellType, IndexPath, E) -> Void - open class RxCollectionViewRealmDataSource : NSObject, UICollectionViewDataSource { + open class RxCollectionViewRealmDataSource : NSObject, UICollectionViewDataSource, SectionedViewDataSourceType { private var items: AnyRealmCollection? // MARK: - Configuration @@ -47,7 +47,7 @@ import RxRealm } // MARK: - Data access - public func model(at indexPath: IndexPath) -> E { + public func model(at indexPath: IndexPath) throws -> Any { return items![indexPath.row] } diff --git a/Pod/Classes/RxTableViewRealmDataSource.swift b/Pod/Classes/RxTableViewRealmDataSource.swift index c80bb80..a77454e 100644 --- a/Pod/Classes/RxTableViewRealmDataSource.swift +++ b/Pod/Classes/RxTableViewRealmDataSource.swift @@ -20,7 +20,7 @@ import RxRealm public typealias TableCellFactory = (RxTableViewRealmDataSource, UITableView, IndexPath, E) -> UITableViewCell public typealias TableCellConfig = (CellType, IndexPath, E) -> Void - open class RxTableViewRealmDataSource: NSObject, UITableViewDataSource { + open class RxTableViewRealmDataSource: NSObject, UITableViewDataSource, SectionedViewDataSourceType { private var items: AnyRealmCollection? @@ -55,7 +55,7 @@ import RxRealm } // MARK: - Data access - public func model(at indexPath: IndexPath) -> E { + public func model(at indexPath: IndexPath) throws -> Any { return items![indexPath.row] } From 42c8757310c4f10076dfc7e1225203dc50d65fbd Mon Sep 17 00:00:00 2001 From: Vasyl Myronchuk Date: Tue, 13 Feb 2018 13:28:07 +0200 Subject: [PATCH 2/6] Restored previous declarations with deprecation to allow more smooth transition. Corrected README --- Pod/Classes/Reactive+RxRealmDataSources.swift | 10 ++++++++++ README.md | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Pod/Classes/Reactive+RxRealmDataSources.swift b/Pod/Classes/Reactive+RxRealmDataSources.swift index 1093289..e16e224 100644 --- a/Pod/Classes/Reactive+RxRealmDataSources.swift +++ b/Pod/Classes/Reactive+RxRealmDataSources.swift @@ -29,6 +29,11 @@ extension Reactive where Base: UITableView { ds.applyChanges(items: AnyRealmCollection(results), changes: changes) } } + + @available(*, deprecated, renamed: "modelSelected") + public func realmModelSelected(_ modelType: E.Type) -> ControlEvent where E: RealmSwift.Object { + return modelSelected(modelType) + } } extension Reactive where Base: UICollectionView { @@ -44,6 +49,11 @@ extension Reactive where Base: UICollectionView { ds.applyChanges(items: AnyRealmCollection(results), changes: changes) } } + + @available(*, deprecated, renamed: "modelSelected") + public func realmModelSelected(_ modelType: E.Type) -> ControlEvent where E: RealmSwift.Object { + return modelSelected(modelType) + } } diff --git a/README.md b/README.md index 69fb1ea..07ba9de 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ laps The library adds an extension to table views and collection views, allowing you to easily subscribe to the cell selected delegate event. Here's a snippet from the example demo app: ```swift -tableView.rx.realmModelSelected(Lap.self) +tableView.rx.modelSelected(Lap.self) .map({ $0.text }) .bind(to: rx.title) .disposed(by: bag) From 95d994a3ee05be74d99d8918537e47d2e1b6ad47 Mon Sep 17 00:00:00 2001 From: Vasyl Myronchuk Date: Sat, 17 Feb 2018 13:22:05 +0200 Subject: [PATCH 3/6] iOS data sources reworked slightly to follow RxCocoa conventions - most importantly, leveraging underlying DelegateProxy mechanism. This has multiple benefits: allows to use standard RxCocoa binding interface instead of writing our own; and brings functionality that otherwise was broken (like observing dataSource method invocations). --- Pod/Classes/Reactive+RxRealmDataSources.swift | 39 +++++++------- .../RxCollectionViewRealmDataSource.swift | 52 ++++++++++--------- Pod/Classes/RxTableViewRealmDataSource.swift | 16 +++--- README.md | 4 +- 4 files changed, 57 insertions(+), 54 deletions(-) diff --git a/Pod/Classes/Reactive+RxRealmDataSources.swift b/Pod/Classes/Reactive+RxRealmDataSources.swift index e16e224..4ebe663 100644 --- a/Pod/Classes/Reactive+RxRealmDataSources.swift +++ b/Pod/Classes/Reactive+RxRealmDataSources.swift @@ -12,22 +12,23 @@ import RxSwift import RxCocoa import RxRealm +public typealias RealmChange = (AnyRealmCollection, RealmChangeset?) + #if os(iOS) // MARK: - iOS / UIKit import UIKit extension Reactive where Base: UITableView { - public func realmChanges(_ dataSource: RxTableViewRealmDataSource) - -> RealmBindObserver, RxTableViewRealmDataSource> { - - return RealmBindObserver(dataSource: dataSource) {ds, results, changes in - if ds.tableView == nil { - ds.tableView = self.base - } - ds.tableView?.dataSource = ds - ds.applyChanges(items: AnyRealmCollection(results), changes: changes) - } + @available(*, deprecated, message: "use items(dataSource:) instead") + public func realmChanges< + DataSource: RxTableViewDataSourceType & UITableViewDataSource, + O: ObservableType> + (_ dataSource: DataSource) + -> (_ source: O) + -> Disposable + where DataSource.Element == O.E { + return items(dataSource: dataSource) } @available(*, deprecated, renamed: "modelSelected") @@ -38,16 +39,14 @@ extension Reactive where Base: UITableView { extension Reactive where Base: UICollectionView { - public func realmChanges(_ dataSource: RxCollectionViewRealmDataSource) - -> RealmBindObserver, RxCollectionViewRealmDataSource> { - - return RealmBindObserver(dataSource: dataSource) {ds, results, changes in - if ds.collectionView == nil { - ds.collectionView = self.base - } - ds.collectionView?.dataSource = ds - ds.applyChanges(items: AnyRealmCollection(results), changes: changes) - } + @available(*, deprecated, message: "use items(dataSource:) instead") + public func realmChanges< + DataSource: RxCollectionViewDataSourceType & UICollectionViewDataSource, + O: ObservableType> + (_ dataSource: DataSource) + -> (_ source: O) + -> Disposable where DataSource.Element == O.E { + return items(dataSource: dataSource) } @available(*, deprecated, renamed: "modelSelected") diff --git a/Pod/Classes/RxCollectionViewRealmDataSource.swift b/Pod/Classes/RxCollectionViewRealmDataSource.swift index e6d5f80..f738a25 100644 --- a/Pod/Classes/RxCollectionViewRealmDataSource.swift +++ b/Pod/Classes/RxCollectionViewRealmDataSource.swift @@ -20,12 +20,11 @@ import RxRealm public typealias CollectionCellFactory = (RxCollectionViewRealmDataSource, UICollectionView, IndexPath, E) -> UICollectionViewCell public typealias CollectionCellConfig = (CellType, IndexPath, E) -> Void - open class RxCollectionViewRealmDataSource : NSObject, UICollectionViewDataSource, SectionedViewDataSourceType { + open class RxCollectionViewRealmDataSource : NSObject, UICollectionViewDataSource, SectionedViewDataSourceType, RxCollectionViewDataSourceType { private var items: AnyRealmCollection? // MARK: - Configuration - public var collectionView: UICollectionView? public var animated = true // MARK: - Init @@ -51,6 +50,13 @@ import RxRealm return items![indexPath.row] } + // MARK: - RxCollectionViewDataSourceType + public func collectionView(_ collectionView: UICollectionView, observedEvent: Event>) { + Binder(self) { dataSource, element in + dataSource.applyChanges(to: collectionView, items: element.0, changes: element.1) + }.on(observedEvent) + } + // MARK: - UICollectionViewDataSource protocol public func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 @@ -67,38 +73,34 @@ import RxRealm // MARK: - Applying changeset to the collection view private let fromRow = {(row: Int) in return IndexPath(row: row, section: 0)} - func applyChanges(items: AnyRealmCollection, changes: RealmChangeset?) { + func applyChanges(to collectionView: UICollectionView, items: AnyRealmCollection, changes: RealmChangeset?) { if self.items == nil { self.items = items } - guard let collectionView = collectionView else { - fatalError("You have to bind a collection view to the data source.") - } + guard animated else { + collectionView.reloadData() + return + } - guard animated else { - collectionView.reloadData() - return - } + guard let changes = changes else { + collectionView.reloadData() + return + } - guard let changes = changes else { - collectionView.reloadData() - return - } + let lastItemCount = collectionView.numberOfItems(inSection: 0) + guard items.count == lastItemCount + changes.inserted.count - changes.deleted.count else { + collectionView.reloadData() + return + } - let lastItemCount = collectionView.numberOfItems(inSection: 0) - guard items.count == lastItemCount + changes.inserted.count - changes.deleted.count else { - collectionView.reloadData() - return + collectionView.performBatchUpdates({[unowned self] in + collectionView.deleteItems(at: changes.deleted.map(self.fromRow)) + collectionView.reloadItems(at: changes.updated.map(self.fromRow)) + collectionView.insertItems(at: changes.inserted.map(self.fromRow)) + }, completion: nil) } - - collectionView.performBatchUpdates({[unowned self] in - collectionView.deleteItems(at: changes.deleted.map(self.fromRow)) - collectionView.reloadItems(at: changes.updated.map(self.fromRow)) - collectionView.insertItems(at: changes.inserted.map(self.fromRow)) - }, completion: nil) } -} #elseif os(OSX) // MARK: - macOS / Cocoa diff --git a/Pod/Classes/RxTableViewRealmDataSource.swift b/Pod/Classes/RxTableViewRealmDataSource.swift index a77454e..4b63767 100644 --- a/Pod/Classes/RxTableViewRealmDataSource.swift +++ b/Pod/Classes/RxTableViewRealmDataSource.swift @@ -20,13 +20,12 @@ import RxRealm public typealias TableCellFactory = (RxTableViewRealmDataSource, UITableView, IndexPath, E) -> UITableViewCell public typealias TableCellConfig = (CellType, IndexPath, E) -> Void - open class RxTableViewRealmDataSource: NSObject, UITableViewDataSource, SectionedViewDataSourceType { + open class RxTableViewRealmDataSource: NSObject, UITableViewDataSource, SectionedViewDataSourceType, RxTableViewDataSourceType { private var items: AnyRealmCollection? // MARK: - Configuration - public var tableView: UITableView? public var animated = true public var rowAnimations = ( insert: UITableView.RowAnimation.automatic, @@ -59,6 +58,13 @@ import RxRealm return items![indexPath.row] } + // MARK: - RxTableViewDataSourceType + public func tableView(_ tableView: UITableView, observedEvent: Event>) { + Binder(self) { dataSource, element in + dataSource.applyChanges(to: tableView, items: element.0, changes: element.1) + }.on(observedEvent) + } + // MARK: - UITableViewDataSource protocol public func numberOfSections(in tableView: UITableView) -> Int { return 1 @@ -83,15 +89,11 @@ import RxRealm // MARK: - Applying changeset to the table view private let fromRow = {(row: Int) in return IndexPath(row: row, section: 0)} - func applyChanges(items: AnyRealmCollection, changes: RealmChangeset?) { + func applyChanges(to tableView: UITableView, items: AnyRealmCollection, changes: RealmChangeset?) { if self.items == nil { self.items = items } - guard let tableView = tableView else { - fatalError("You have to bind a table view to the data source.") - } - guard animated else { tableView.reloadData() return diff --git a/README.md b/README.md index 07ba9de..c3b256f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ let laps = Observable.changeset(from: realm.objects(Timer.self).first!.laps) // bind to table view laps - .bindTo(tableView.rx.realmChanges(dataSource)) + .bindTo(tableView.rx.items(dataSource: dataSource)) .disposed(by: bag) ``` @@ -52,7 +52,7 @@ let laps = Observable.changeset(from: realm.objects(Timer.self).first!.laps) // bind to collection view laps - .bindTo(collectionView.rx.realmChanges(dataSource)) + .bindTo(collectionView.rx.items(dataSource: dataSource)) .disposed(by: bag) ``` From 021988de1fdb5515ed9d71abdb3da852076e14de Mon Sep 17 00:00:00 2001 From: Vasyl Myronchuk Date: Sat, 17 Feb 2018 13:22:36 +0200 Subject: [PATCH 4/6] Examples updated to use standard binding. Introduced basic editing to show functionality that wasn't possible before (e.g., it was not possible to observe itemDeleted in previous implementation). --- .../Base.lproj/Main.storyboard | 21 +++++++----- .../CollectionViewController.swift | 33 +++++++++++++++++-- .../RxRealmDataSources/DataRandomizer.swift | 6 +++- .../RxRealmDataSources/ViewController.swift | 23 ++++++++++++- 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/Example/RxRealmDataSources/Base.lproj/Main.storyboard b/Example/RxRealmDataSources/Base.lproj/Main.storyboard index ab3256e..323aa3e 100644 --- a/Example/RxRealmDataSources/Base.lproj/Main.storyboard +++ b/Example/RxRealmDataSources/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -22,9 +22,8 @@ - - - + + @@ -58,6 +57,12 @@ + + + + + + @@ -141,7 +146,7 @@ - + @@ -168,7 +173,7 @@ - +