Skip to content

Commit

Permalink
Add Support for DiffableDataSource
Browse files Browse the repository at this point in the history
  • Loading branch information
mlch911 committed Jun 7, 2023
1 parent 95917a5 commit 00d6467
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 51 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ All notable changes to this project will be documented in this file.
* Renames 'OSXApplicationExtension' to 'macOSApplicationExtension' in Availability Check.
* Provides `Infallible` versions of `combineLatest` without `resultSelector` requirement.
* Provides `Infallible` versions of `CombineLatest+Collection` helpers.
* Explicitly declare `APPLICATION_EXTENSION_API_ONLY` for CocoaPods
* Explicitly declare `APPLICATION_EXTENSION_API_ONLY` for CocoaPods
* Support Use DiffableDataSource with RxCocoa like `UITableViewDiffableDataSource` or `UICollectionViewDiffableDataSource`.

## 6.5.0

Expand Down
12 changes: 12 additions & 0 deletions RxCocoa/iOS/Protocols/RxDiffableDataSourceType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// RxDiffableDataSourceType.swift
// RxCocoa
//
// Created by mlch911 on 2023/6/7.
//

import Foundation

protocol RxDiffableDataSourceType {
func model(for indexPath: IndexPath) -> Any?
}
33 changes: 33 additions & 0 deletions RxCocoa/iOS/UICollectionView+DiffableDataSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// UICollectionView+DiffableDataSource.swift
// RxCocoa
//
// Created by mlch911 on 2023/6/1.
//

import Foundation

extension UICollectionView {
func isDiffableDataSource() -> Bool {
diffableDataSource() != nil
}

func diffableDataSource() -> RxDiffableDataSourceType? {
if #available(iOS 13.0, tvOS 13.0, *) {
return dataSource as? RxDiffableDataSourceType
}
return nil
}
}

extension UICollectionViewDiffableDataSourceReference: RxDiffableDataSourceType {
func model(for indexPath: IndexPath) -> Any? {
itemIdentifier(for: indexPath)
}
}

extension UICollectionViewDiffableDataSource: RxDiffableDataSourceType {
func model(for indexPath: IndexPath) -> Any? {
itemIdentifier(for: indexPath)
}
}
65 changes: 45 additions & 20 deletions RxCocoa/iOS/UICollectionView+Rx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,22 +139,31 @@ extension Reactive where Base: UICollectionView {
-> (_ source: Source)
-> Disposable where DataSource.Element == Source.Element
{
return { source in
// This is called for side effects only, and to make sure delegate proxy is in place when
// data source is being bound.
// This is needed because theoretically the data source subscription itself might
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
// setting data source will set delegate, and UICollectionView might get into a weird state.
// Therefore it's better to set delegate proxy first, just to be sure.
_ = self.delegate
// Strong reference is needed because data source is in use until result subscription is disposed
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource, retainDataSource: true) { [weak collectionView = self.base] (_: RxCollectionViewDataSourceProxy, event) -> Void in
guard let collectionView = collectionView else {
return
}
dataSource.collectionView(collectionView, observedEvent: event)
}
}
if base.isDiffableDataSource() {
return { source in
_ = self.delegate
return source.subscribe { [weak collectionView = self.base] event -> Void in
guard let collectionView = collectionView else { return }
dataSource.collectionView(collectionView, observedEvent: event)
}
}
}
return { source in
// This is called for side effects only, and to make sure delegate proxy is in place when
// data source is being bound.
// This is needed because theoretically the data source subscription itself might
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
// setting data source will set delegate, and UICollectionView might get into a weird state.
// Therefore it's better to set delegate proxy first, just to be sure.
_ = self.delegate
// Strong reference is needed because data source is in use until result subscription is disposed
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource, retainDataSource: true) { [weak collectionView = self.base] (_: RxCollectionViewDataSourceProxy, event) -> Void in
guard let collectionView = collectionView else {
return
}
dataSource.collectionView(collectionView, observedEvent: event)
}
}
}
}

Expand All @@ -166,8 +175,15 @@ extension Reactive where Base: UICollectionView {
///
/// For more information take a look at `DelegateProxyType` protocol documentation.
public var dataSource: DelegateProxy<UICollectionView, UICollectionViewDataSource> {
RxCollectionViewDataSourceProxy.proxy(for: base)
fatalErrorIfDiffableDataSource()
return RxCollectionViewDataSourceProxy.proxy(for: base)
}

private func fatalErrorIfDiffableDataSource(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) {
if base.isDiffableDataSource() {
fatalError(message(), file: file, line: line)
}
}

/// Installs data source as forwarding delegate on `rx.dataSource`.
/// Data source won't be retained.
Expand Down Expand Up @@ -309,9 +325,18 @@ extension Reactive where Base: UICollectionView {

/// Synchronous helper method for retrieving a model at indexPath through a reactive data source
public func model<T>(at indexPath: IndexPath) throws -> T {
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemsWith*` methods was used.")

let element = try dataSource.model(at: indexPath)
let element: Any

if #available(iOS 13.0, tvOS 13.0, *), let dataSource = base.diffableDataSource() {
guard let item = dataSource.model(for: indexPath) else {
throw RxCocoaError.itemsNotYetBound(object: dataSource)
}
element = item
} else {
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemsWith*` methods was used.")

element = try dataSource.model(at: indexPath)
}

return try castOrThrow(T.self, element)
}
Expand Down
33 changes: 33 additions & 0 deletions RxCocoa/iOS/UITableView+DiffableDataSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// UITableView+DiffableDataSource.swift
// RxCocoa
//
// Created by mlch911 on 2023/6/7.
//

import Foundation

extension UITableView {
func isDiffableDataSource() -> Bool {
diffableDataSource() != nil
}

func diffableDataSource() -> RxDiffableDataSourceType? {
if #available(iOS 13.0, tvOS 13.0, *) {
return dataSource as? RxDiffableDataSourceType
}
return nil
}
}

extension UITableViewDiffableDataSourceReference: RxDiffableDataSourceType {
func model(for indexPath: IndexPath) -> Any? {
itemIdentifier(for: indexPath)
}
}

extension UITableViewDiffableDataSource: RxDiffableDataSourceType {
func model(for indexPath: IndexPath) -> Any? {
itemIdentifier(for: indexPath)
}
}
100 changes: 70 additions & 30 deletions RxCocoa/iOS/UITableView+Rx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,22 +111,31 @@ extension Reactive where Base: UITableView {
-> (_ source: Source)
-> Disposable
where DataSource.Element == Source.Element {
return { source in
// This is called for side effects only, and to make sure delegate proxy is in place when
// data source is being bound.
// This is needed because theoretically the data source subscription itself might
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
// setting data source will set delegate, and UITableView might get into a weird state.
// Therefore it's better to set delegate proxy first, just to be sure.
_ = self.delegate
// Strong reference is needed because data source is in use until result subscription is disposed
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource as UITableViewDataSource, retainDataSource: true) { [weak tableView = self.base] (_: RxTableViewDataSourceProxy, event) -> Void in
guard let tableView = tableView else {
return
}
dataSource.tableView(tableView, observedEvent: event)
}
}
if base.isDiffableDataSource() {
return { source in
_ = self.delegate
return source.subscribe { [weak tableView = self.base] event -> Void in
guard let tableView = tableView else { return }
dataSource.tableView(tableView, observedEvent: event)
}
}
}
return { source in
// This is called for side effects only, and to make sure delegate proxy is in place when
// data source is being bound.
// This is needed because theoretically the data source subscription itself might
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
// setting data source will set delegate, and UITableView might get into a weird state.
// Therefore it's better to set delegate proxy first, just to be sure.
_ = self.delegate
// Strong reference is needed because data source is in use until result subscription is disposed
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource as UITableViewDataSource, retainDataSource: true) { [weak tableView = self.base] (_: RxTableViewDataSourceProxy, event) -> Void in
guard let tableView = tableView else {
return
}
dataSource.tableView(tableView, observedEvent: event)
}
}
}

}
Expand All @@ -138,8 +147,31 @@ extension Reactive where Base: UITableView {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var dataSource: DelegateProxy<UITableView, UITableViewDataSource> {
RxTableViewDataSourceProxy.proxy(for: base)
fatalErrorIfDiffableDataSource()
return RxTableViewDataSourceProxy.proxy(for: base)
}

private func fatalErrorIfDiffableDataSource(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) {
if base.isDiffableDataSource() {
fatalError(message(), file: file, line: line)
}
}

private var commitEditingStyleMethodInvoked: Observable<[Any]> {
dataSourceMethodInvoked(for: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:)))
}

private var moveRowAtMethodInvoked: Observable<[Any]> {
dataSourceMethodInvoked(for: #selector(UITableViewDataSource.tableView(_:moveRowAt:to:)))
}

private func dataSourceMethodInvoked(for selector: Selector) -> Observable<[Any]> {
if self.base.isDiffableDataSource() {
return (self.base.dataSource as! NSObject).rx.methodInvoked(selector)
} else {
return self.dataSource.methodInvoked(selector)
}
}

/**
Installs data source as forwarding delegate on `rx.dataSource`.
Expand Down Expand Up @@ -221,22 +253,21 @@ extension Reactive where Base: UITableView {
Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`.
*/
public var itemInserted: ControlEvent<IndexPath> {
let source = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:commit:forRowAt:)))
.filter { a in
return UITableViewCell.EditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .insert
}
.map { a in
return (try castOrThrow(IndexPath.self, a[2]))
}
let indexSource = commitEditingStyleMethodInvoked
.filter { a in
return UITableViewCell.EditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .insert
}.map { a in
return (try castOrThrow(IndexPath.self, a[2]))
}

return ControlEvent(events: source)
return ControlEvent(events: indexSource)
}

/**
Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`.
*/
public var itemDeleted: ControlEvent<IndexPath> {
let source = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:commit:forRowAt:)))
let source = commitEditingStyleMethodInvoked
.filter { a in
return UITableViewCell.EditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .delete
}
Expand All @@ -251,7 +282,7 @@ extension Reactive where Base: UITableView {
Reactive wrapper for `delegate` message `tableView:moveRowAtIndexPath:toIndexPath:`.
*/
public var itemMoved: ControlEvent<ItemMovedEvent> {
let source: Observable<ItemMovedEvent> = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:moveRowAt:to:)))
let source: Observable<ItemMovedEvent> = moveRowAtMethodInvoked
.map { a in
return (try castOrThrow(IndexPath.self, a[1]), try castOrThrow(IndexPath.self, a[2]))
}
Expand Down Expand Up @@ -356,9 +387,18 @@ extension Reactive where Base: UITableView {
Synchronous helper method for retrieving a model at indexPath through a reactive data source.
*/
public func model<T>(at indexPath: IndexPath) throws -> T {
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.items*` methods was used.")

let element = try dataSource.model(at: indexPath)
let element: Any

if let dataSource = base.diffableDataSource() {
guard let item = dataSource.model(for: indexPath) else {
throw RxCocoaError.itemsNotYetBound(object: dataSource)
}
element = item
} else {
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.items*` methods was used.")

element = try dataSource.model(at: indexPath)
}

return castOrFatalError(element)
}
Expand Down

0 comments on commit 00d6467

Please sign in to comment.