Skip to content

Commit 00d6467

Browse files
committed
Add Support for DiffableDataSource
1 parent 95917a5 commit 00d6467

File tree

6 files changed

+195
-51
lines changed

6 files changed

+195
-51
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ All notable changes to this project will be documented in this file.
1010
* Renames 'OSXApplicationExtension' to 'macOSApplicationExtension' in Availability Check.
1111
* Provides `Infallible` versions of `combineLatest` without `resultSelector` requirement.
1212
* Provides `Infallible` versions of `CombineLatest+Collection` helpers.
13-
* Explicitly declare `APPLICATION_EXTENSION_API_ONLY` for CocoaPods
13+
* Explicitly declare `APPLICATION_EXTENSION_API_ONLY` for CocoaPods
14+
* Support Use DiffableDataSource with RxCocoa like `UITableViewDiffableDataSource` or `UICollectionViewDiffableDataSource`.
1415

1516
## 6.5.0
1617

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// RxDiffableDataSourceType.swift
3+
// RxCocoa
4+
//
5+
// Created by mlch911 on 2023/6/7.
6+
//
7+
8+
import Foundation
9+
10+
protocol RxDiffableDataSourceType {
11+
func model(for indexPath: IndexPath) -> Any?
12+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// UICollectionView+DiffableDataSource.swift
3+
// RxCocoa
4+
//
5+
// Created by mlch911 on 2023/6/1.
6+
//
7+
8+
import Foundation
9+
10+
extension UICollectionView {
11+
func isDiffableDataSource() -> Bool {
12+
diffableDataSource() != nil
13+
}
14+
15+
func diffableDataSource() -> RxDiffableDataSourceType? {
16+
if #available(iOS 13.0, tvOS 13.0, *) {
17+
return dataSource as? RxDiffableDataSourceType
18+
}
19+
return nil
20+
}
21+
}
22+
23+
extension UICollectionViewDiffableDataSourceReference: RxDiffableDataSourceType {
24+
func model(for indexPath: IndexPath) -> Any? {
25+
itemIdentifier(for: indexPath)
26+
}
27+
}
28+
29+
extension UICollectionViewDiffableDataSource: RxDiffableDataSourceType {
30+
func model(for indexPath: IndexPath) -> Any? {
31+
itemIdentifier(for: indexPath)
32+
}
33+
}

RxCocoa/iOS/UICollectionView+Rx.swift

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -139,22 +139,31 @@ extension Reactive where Base: UICollectionView {
139139
-> (_ source: Source)
140140
-> Disposable where DataSource.Element == Source.Element
141141
{
142-
return { source in
143-
// This is called for side effects only, and to make sure delegate proxy is in place when
144-
// data source is being bound.
145-
// This is needed because theoretically the data source subscription itself might
146-
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
147-
// setting data source will set delegate, and UICollectionView might get into a weird state.
148-
// Therefore it's better to set delegate proxy first, just to be sure.
149-
_ = self.delegate
150-
// Strong reference is needed because data source is in use until result subscription is disposed
151-
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource, retainDataSource: true) { [weak collectionView = self.base] (_: RxCollectionViewDataSourceProxy, event) -> Void in
152-
guard let collectionView = collectionView else {
153-
return
154-
}
155-
dataSource.collectionView(collectionView, observedEvent: event)
156-
}
157-
}
142+
if base.isDiffableDataSource() {
143+
return { source in
144+
_ = self.delegate
145+
return source.subscribe { [weak collectionView = self.base] event -> Void in
146+
guard let collectionView = collectionView else { return }
147+
dataSource.collectionView(collectionView, observedEvent: event)
148+
}
149+
}
150+
}
151+
return { source in
152+
// This is called for side effects only, and to make sure delegate proxy is in place when
153+
// data source is being bound.
154+
// This is needed because theoretically the data source subscription itself might
155+
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
156+
// setting data source will set delegate, and UICollectionView might get into a weird state.
157+
// Therefore it's better to set delegate proxy first, just to be sure.
158+
_ = self.delegate
159+
// Strong reference is needed because data source is in use until result subscription is disposed
160+
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource, retainDataSource: true) { [weak collectionView = self.base] (_: RxCollectionViewDataSourceProxy, event) -> Void in
161+
guard let collectionView = collectionView else {
162+
return
163+
}
164+
dataSource.collectionView(collectionView, observedEvent: event)
165+
}
166+
}
158167
}
159168
}
160169

@@ -166,8 +175,15 @@ extension Reactive where Base: UICollectionView {
166175
///
167176
/// For more information take a look at `DelegateProxyType` protocol documentation.
168177
public var dataSource: DelegateProxy<UICollectionView, UICollectionViewDataSource> {
169-
RxCollectionViewDataSourceProxy.proxy(for: base)
178+
fatalErrorIfDiffableDataSource()
179+
return RxCollectionViewDataSourceProxy.proxy(for: base)
170180
}
181+
182+
private func fatalErrorIfDiffableDataSource(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) {
183+
if base.isDiffableDataSource() {
184+
fatalError(message(), file: file, line: line)
185+
}
186+
}
171187

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

310326
/// Synchronous helper method for retrieving a model at indexPath through a reactive data source
311327
public func model<T>(at indexPath: IndexPath) throws -> T {
312-
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemsWith*` methods was used.")
313-
314-
let element = try dataSource.model(at: indexPath)
328+
let element: Any
329+
330+
if #available(iOS 13.0, tvOS 13.0, *), let dataSource = base.diffableDataSource() {
331+
guard let item = dataSource.model(for: indexPath) else {
332+
throw RxCocoaError.itemsNotYetBound(object: dataSource)
333+
}
334+
element = item
335+
} else {
336+
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemsWith*` methods was used.")
337+
338+
element = try dataSource.model(at: indexPath)
339+
}
315340

316341
return try castOrThrow(T.self, element)
317342
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// UITableView+DiffableDataSource.swift
3+
// RxCocoa
4+
//
5+
// Created by mlch911 on 2023/6/7.
6+
//
7+
8+
import Foundation
9+
10+
extension UITableView {
11+
func isDiffableDataSource() -> Bool {
12+
diffableDataSource() != nil
13+
}
14+
15+
func diffableDataSource() -> RxDiffableDataSourceType? {
16+
if #available(iOS 13.0, tvOS 13.0, *) {
17+
return dataSource as? RxDiffableDataSourceType
18+
}
19+
return nil
20+
}
21+
}
22+
23+
extension UITableViewDiffableDataSourceReference: RxDiffableDataSourceType {
24+
func model(for indexPath: IndexPath) -> Any? {
25+
itemIdentifier(for: indexPath)
26+
}
27+
}
28+
29+
extension UITableViewDiffableDataSource: RxDiffableDataSourceType {
30+
func model(for indexPath: IndexPath) -> Any? {
31+
itemIdentifier(for: indexPath)
32+
}
33+
}

RxCocoa/iOS/UITableView+Rx.swift

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -111,22 +111,31 @@ extension Reactive where Base: UITableView {
111111
-> (_ source: Source)
112112
-> Disposable
113113
where DataSource.Element == Source.Element {
114-
return { source in
115-
// This is called for side effects only, and to make sure delegate proxy is in place when
116-
// data source is being bound.
117-
// This is needed because theoretically the data source subscription itself might
118-
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
119-
// setting data source will set delegate, and UITableView might get into a weird state.
120-
// Therefore it's better to set delegate proxy first, just to be sure.
121-
_ = self.delegate
122-
// Strong reference is needed because data source is in use until result subscription is disposed
123-
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource as UITableViewDataSource, retainDataSource: true) { [weak tableView = self.base] (_: RxTableViewDataSourceProxy, event) -> Void in
124-
guard let tableView = tableView else {
125-
return
126-
}
127-
dataSource.tableView(tableView, observedEvent: event)
128-
}
129-
}
114+
if base.isDiffableDataSource() {
115+
return { source in
116+
_ = self.delegate
117+
return source.subscribe { [weak tableView = self.base] event -> Void in
118+
guard let tableView = tableView else { return }
119+
dataSource.tableView(tableView, observedEvent: event)
120+
}
121+
}
122+
}
123+
return { source in
124+
// This is called for side effects only, and to make sure delegate proxy is in place when
125+
// data source is being bound.
126+
// This is needed because theoretically the data source subscription itself might
127+
// call `self.rx.delegate`. If that happens, it might cause weird side effects since
128+
// setting data source will set delegate, and UITableView might get into a weird state.
129+
// Therefore it's better to set delegate proxy first, just to be sure.
130+
_ = self.delegate
131+
// Strong reference is needed because data source is in use until result subscription is disposed
132+
return source.subscribeProxyDataSource(ofObject: self.base, dataSource: dataSource as UITableViewDataSource, retainDataSource: true) { [weak tableView = self.base] (_: RxTableViewDataSourceProxy, event) -> Void in
133+
guard let tableView = tableView else {
134+
return
135+
}
136+
dataSource.tableView(tableView, observedEvent: event)
137+
}
138+
}
130139
}
131140

132141
}
@@ -138,8 +147,31 @@ extension Reactive where Base: UITableView {
138147
For more information take a look at `DelegateProxyType` protocol documentation.
139148
*/
140149
public var dataSource: DelegateProxy<UITableView, UITableViewDataSource> {
141-
RxTableViewDataSourceProxy.proxy(for: base)
150+
fatalErrorIfDiffableDataSource()
151+
return RxTableViewDataSourceProxy.proxy(for: base)
142152
}
153+
154+
private func fatalErrorIfDiffableDataSource(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) {
155+
if base.isDiffableDataSource() {
156+
fatalError(message(), file: file, line: line)
157+
}
158+
}
159+
160+
private var commitEditingStyleMethodInvoked: Observable<[Any]> {
161+
dataSourceMethodInvoked(for: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:)))
162+
}
163+
164+
private var moveRowAtMethodInvoked: Observable<[Any]> {
165+
dataSourceMethodInvoked(for: #selector(UITableViewDataSource.tableView(_:moveRowAt:to:)))
166+
}
167+
168+
private func dataSourceMethodInvoked(for selector: Selector) -> Observable<[Any]> {
169+
if self.base.isDiffableDataSource() {
170+
return (self.base.dataSource as! NSObject).rx.methodInvoked(selector)
171+
} else {
172+
return self.dataSource.methodInvoked(selector)
173+
}
174+
}
143175

144176
/**
145177
Installs data source as forwarding delegate on `rx.dataSource`.
@@ -221,22 +253,21 @@ extension Reactive where Base: UITableView {
221253
Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`.
222254
*/
223255
public var itemInserted: ControlEvent<IndexPath> {
224-
let source = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:commit:forRowAt:)))
225-
.filter { a in
226-
return UITableViewCell.EditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .insert
227-
}
228-
.map { a in
229-
return (try castOrThrow(IndexPath.self, a[2]))
230-
}
256+
let indexSource = commitEditingStyleMethodInvoked
257+
.filter { a in
258+
return UITableViewCell.EditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .insert
259+
}.map { a in
260+
return (try castOrThrow(IndexPath.self, a[2]))
261+
}
231262

232-
return ControlEvent(events: source)
263+
return ControlEvent(events: indexSource)
233264
}
234265

235266
/**
236267
Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`.
237268
*/
238269
public var itemDeleted: ControlEvent<IndexPath> {
239-
let source = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:commit:forRowAt:)))
270+
let source = commitEditingStyleMethodInvoked
240271
.filter { a in
241272
return UITableViewCell.EditingStyle(rawValue: (try castOrThrow(NSNumber.self, a[1])).intValue) == .delete
242273
}
@@ -251,7 +282,7 @@ extension Reactive where Base: UITableView {
251282
Reactive wrapper for `delegate` message `tableView:moveRowAtIndexPath:toIndexPath:`.
252283
*/
253284
public var itemMoved: ControlEvent<ItemMovedEvent> {
254-
let source: Observable<ItemMovedEvent> = self.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:moveRowAt:to:)))
285+
let source: Observable<ItemMovedEvent> = moveRowAtMethodInvoked
255286
.map { a in
256287
return (try castOrThrow(IndexPath.self, a[1]), try castOrThrow(IndexPath.self, a[2]))
257288
}
@@ -356,9 +387,18 @@ extension Reactive where Base: UITableView {
356387
Synchronous helper method for retrieving a model at indexPath through a reactive data source.
357388
*/
358389
public func model<T>(at indexPath: IndexPath) throws -> T {
359-
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.items*` methods was used.")
360-
361-
let element = try dataSource.model(at: indexPath)
390+
let element: Any
391+
392+
if let dataSource = base.diffableDataSource() {
393+
guard let item = dataSource.model(for: indexPath) else {
394+
throw RxCocoaError.itemsNotYetBound(object: dataSource)
395+
}
396+
element = item
397+
} else {
398+
let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.items*` methods was used.")
399+
400+
element = try dataSource.model(at: indexPath)
401+
}
362402

363403
return castOrFatalError(element)
364404
}

0 commit comments

Comments
 (0)