Skip to content

Commit 508fa8b

Browse files
committed
Add Support for DiffableDataSource
1 parent bbfea38 commit 508fa8b

6 files changed

+488
-0
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// DiffableSectionModel.swift
3+
//
4+
//
5+
// Created by mlch911 on 2023/5/30.
6+
//
7+
8+
import Foundation
9+
10+
public struct DiffableSectionModel<Section: Hashable, ItemType: Hashable> {
11+
public var model: Section
12+
public var items: [Item]
13+
14+
public init(model: Section, items: [Item]) {
15+
self.model = model
16+
self.items = items
17+
}
18+
}
19+
20+
extension DiffableSectionModel: DiffableSectionModelType {
21+
public typealias Identity = Section
22+
public typealias Item = ItemType
23+
24+
public var identity: Section {
25+
return model
26+
}
27+
}
28+
29+
extension DiffableSectionModel
30+
: CustomStringConvertible {
31+
32+
public var description: String {
33+
return "\(self.model) > \(items)"
34+
}
35+
}
36+
37+
extension DiffableSectionModel {
38+
public init(original: DiffableSectionModel<Section, Item>, items: [Item]) {
39+
self.model = original.model
40+
self.items = items
41+
}
42+
}
43+
44+
extension DiffableSectionModel
45+
: Equatable {
46+
47+
public static func == (lhs: DiffableSectionModel, rhs: DiffableSectionModel) -> Bool {
48+
return lhs.model == rhs.model
49+
&& lhs.items == rhs.items
50+
}
51+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// DiffableSectionModelType.swift
3+
//
4+
//
5+
// Created by mlch911 on 2023/5/30.
6+
//
7+
8+
import Foundation
9+
10+
public protocol DiffableSectionModelType: SectionModelType, Hashable where Item: Hashable {}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//
2+
// CollectionViewDiffableDataSource.swift
3+
//
4+
//
5+
// Created by mlch911 on 2023/5/30.
6+
//
7+
8+
#if os(iOS) || os(tvOS)
9+
import Foundation
10+
import UIKit
11+
#if !RX_NO_MODULE
12+
import RxSwift
13+
import RxCocoa
14+
#endif
15+
16+
@available(iOS 13.0, tvOS 13.0, *)
17+
open class CollectionViewDiffableDataSource<Section: DiffableSectionModelType>:
18+
UICollectionViewDiffableDataSource<Section, Section.Item> {
19+
20+
public enum Error: Swift.Error {
21+
case outOfBounds(indexPath: IndexPath)
22+
}
23+
24+
public typealias ConfigureCellProvider = (_ dataSource: CollectionViewDiffableDataSource<Section>, _ collectionView: UICollectionView, _ indexPath: IndexPath, Section.Item) -> UICollectionViewCell
25+
public typealias ConfigureSupplementaryViewProvider = (_ dataSource: CollectionViewDiffableDataSource<Section>, _ collectionView: UICollectionView, _ elementKind: String, _ indexPath: IndexPath) -> UICollectionReusableView?
26+
public typealias MoveItemProvider = (_ dataSource: CollectionViewDiffableDataSource<Section>, _ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void
27+
public typealias CanMoveItemAtIndexPathProvider = (_ dataSource: CollectionViewDiffableDataSource<Section>, _ indexPath: IndexPath) -> Bool
28+
29+
open var configureSupplementaryView: ConfigureSupplementaryViewProvider?
30+
open var canMoveItemAtIndexPath: CanMoveItemAtIndexPathProvider?
31+
open var moveItem: MoveItemProvider?
32+
33+
public init(
34+
collectionView: UICollectionView,
35+
configureCell: @escaping ConfigureCellProvider,
36+
configureSupplementaryView: @escaping ConfigureSupplementaryViewProvider = { _, _, _, _ in nil },
37+
moveItem: @escaping MoveItemProvider = { _, _, _ in () },
38+
canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPathProvider = { _, _ in true }
39+
) {
40+
self.moveItem = moveItem
41+
self.canMoveItemAtIndexPath = canMoveItemAtIndexPath
42+
self.configureSupplementaryView = configureSupplementaryView
43+
weak var dataSource: CollectionViewDiffableDataSource<Section>!
44+
super.init(collectionView: collectionView, cellProvider: {
45+
configureCell(dataSource, $0, $1, $2)
46+
})
47+
self.supplementaryViewProvider = { [unowned self] in
48+
self.configureSupplementaryView?(self, $0, $1, $2)
49+
}
50+
dataSource = self
51+
}
52+
53+
// MARK: - UICollectionViewDataSource
54+
open override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
55+
canMoveItemAtIndexPath?(self, indexPath) ?? false
56+
}
57+
58+
open override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
59+
self.moveItem?(self, sourceIndexPath, destinationIndexPath)
60+
}
61+
}
62+
63+
extension CollectionViewDiffableDataSource: SectionedViewDataSourceType {
64+
open func model(at indexPath: IndexPath) throws -> Any {
65+
guard let item = itemIdentifier(for: indexPath) else { throw Error.outOfBounds(indexPath: indexPath) }
66+
return item
67+
}
68+
69+
open func sectionModel(at index: Int) -> Section? {
70+
if #available(iOS 15.0, tvOS 15.0, *) {
71+
return sectionIdentifier(for: index)
72+
}
73+
let sections = snapshot().sectionIdentifiers
74+
guard index >= 0 && index < sections.count else { return nil }
75+
return sections[index]
76+
}
77+
78+
open subscript(section: Int) -> Section? {
79+
sectionModel(at: section)
80+
}
81+
82+
open subscript(indexPath: IndexPath) -> Section.Item? {
83+
itemIdentifier(for: indexPath)
84+
}
85+
}
86+
87+
public extension CollectionViewDiffableDataSource {
88+
func numberOfSections() -> Int {
89+
snapshot().numberOfSections
90+
}
91+
92+
func numberOfItems(in section: Int) -> Int {
93+
let snapshot = snapshot()
94+
guard section >= 0, section < snapshot.sectionIdentifiers.count else { return 0 }
95+
return snapshot.numberOfItems(inSection: snapshot.sectionIdentifiers[section])
96+
}
97+
}
98+
99+
#endif
100+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// RxCollectionViewDiffableDataSource.swift
3+
//
4+
//
5+
// Created by mlch911 on 2023/5/30.
6+
//
7+
8+
#if os(iOS) || os(tvOS)
9+
import Foundation
10+
import UIKit
11+
#if !RX_NO_MODULE
12+
import RxSwift
13+
import RxCocoa
14+
#endif
15+
16+
@available(iOS 13.0, tvOS 13.0, *)
17+
open class RxCollectionViewDiffableDataSource<Section: DiffableSectionModelType>
18+
: CollectionViewDiffableDataSource<Section>
19+
, RxCollectionViewDataSourceType {
20+
public typealias Element = [Section]
21+
22+
open func collectionView(_ collectionView: UICollectionView, observedEvent: RxSwift.Event<[Section]>) {
23+
Binder(self) { dataSource, sections in
24+
var snapshot = NSDiffableDataSourceSnapshot<Section, Section.Item>()
25+
snapshot.appendSections(sections)
26+
sections.forEach { section in
27+
snapshot.appendItems(section.items, toSection: section)
28+
}
29+
dataSource.apply(snapshot, animatingDifferences: false)
30+
}.on(observedEvent)
31+
}
32+
}
33+
34+
@available(iOS 13.0, tvOS 13.0, *)
35+
open class RxCollectionViewAnimatedDiffableDataSource<Section: DiffableSectionModelType>
36+
: CollectionViewDiffableDataSource<Section>
37+
, RxCollectionViewDataSourceType {
38+
public typealias Element = [Section]
39+
public typealias DecideViewTransition = (CollectionViewDiffableDataSource<Section>, UICollectionView, NSDiffableDataSourceSnapshot<Section, Section.Item>) -> ViewTransition
40+
41+
/// Calculates view transition depending on type of changes
42+
open var decideViewTransition: DecideViewTransition
43+
44+
public init(
45+
collectionView: UICollectionView,
46+
decideViewTransition: @escaping DecideViewTransition = { _, _, _ in .animated },
47+
configureCell: @escaping ConfigureCellProvider,
48+
configureSupplementaryView: @escaping ConfigureSupplementaryViewProvider = { _, _, _, _ in nil },
49+
moveItem: @escaping MoveItemProvider = { _, _, _ in () },
50+
canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPathProvider = { _, _ in true }
51+
) {
52+
self.decideViewTransition = decideViewTransition
53+
super.init(collectionView: collectionView,
54+
configureCell: configureCell,
55+
configureSupplementaryView: configureSupplementaryView,
56+
moveItem: moveItem,
57+
canMoveItemAtIndexPath: canMoveItemAtIndexPath)
58+
}
59+
60+
open func collectionView(_ collectionView: UICollectionView, observedEvent: RxSwift.Event<[Section]>) {
61+
Binder(self) { dataSource, sections in
62+
var snapshot = NSDiffableDataSourceSnapshot<Section, Section.Item>()
63+
snapshot.appendSections(sections)
64+
sections.forEach { section in
65+
snapshot.appendItems(section.items, toSection: section)
66+
}
67+
let animated = dataSource.decideViewTransition(dataSource, collectionView, snapshot) == .animated
68+
dataSource.apply(snapshot, animatingDifferences: animated)
69+
}.on(observedEvent)
70+
}
71+
}
72+
73+
#endif
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// RxTableViewDiffableDataSource.swift
3+
//
4+
//
5+
// Created by mlch911 on 2023/5/30.
6+
//
7+
8+
#if os(iOS) || os(tvOS)
9+
import Foundation
10+
import UIKit
11+
#if !RX_NO_MODULE
12+
import RxSwift
13+
import RxCocoa
14+
#endif
15+
16+
@available(iOS 13.0, tvOS 13.0, *)
17+
open class RxTableViewDiffableDataSource<Section: DiffableSectionModelType>
18+
: TableViewDiffableDataSource<Section>
19+
, RxTableViewDataSourceType {
20+
public typealias Element = [Section]
21+
22+
open func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<[Section]>) {
23+
Binder(self) { dataSource, sections in
24+
var snapshot = NSDiffableDataSourceSnapshot<Section, Section.Item>()
25+
snapshot.appendSections(sections)
26+
sections.forEach { section in
27+
snapshot.appendItems(section.items, toSection: section)
28+
}
29+
dataSource.apply(snapshot, animatingDifferences: false)
30+
}.on(observedEvent)
31+
}
32+
}
33+
34+
@available(iOS 13.0, tvOS 13.0, *)
35+
open class RxTableViewAnimatedDiffableDataSource<Section: DiffableSectionModelType>
36+
: TableViewDiffableDataSource<Section>
37+
, RxTableViewDataSourceType {
38+
public typealias Element = [Section]
39+
public typealias DecideViewTransition = (TableViewDiffableDataSource<Section>, UITableView, NSDiffableDataSourceSnapshot<Section, Section.Item>) -> ViewTransition
40+
41+
/// Calculates view transition depending on type of changes
42+
public var decideViewTransition: DecideViewTransition
43+
44+
#if os(iOS)
45+
public init(
46+
tableView: UITableView,
47+
animation: UITableView.RowAnimation = .automatic,
48+
decideViewTransition: @escaping DecideViewTransition = { _, _, _ in .animated },
49+
configureCell: @escaping ConfigureCell,
50+
titleForHeaderInSectionProvider: @escaping TitleForHeaderInSectionProvider = { _, _ in nil },
51+
titleForFooterInSectionProvider: @escaping TitleForFooterInSectionProvider = { _, _ in nil },
52+
canEditRowAtIndexPathProvider: @escaping CanEditRowAtIndexPathProvider = { _, _ in true },
53+
canMoveRowAtIndexPathProvider: @escaping CanMoveRowAtIndexPathProvider = { _, _ in true },
54+
sectionIndexTitles: @escaping SectionIndexTitlesProvider = { _ in nil },
55+
sectionForSectionIndexTitle: @escaping SectionForSectionIndexTitleProvider = { _, _, index in index }
56+
) {
57+
self.decideViewTransition = decideViewTransition
58+
super.init(tableView: tableView,
59+
configureCell: configureCell,
60+
titleForHeaderInSectionProvider: titleForHeaderInSectionProvider,
61+
titleForFooterInSectionProvider: titleForFooterInSectionProvider,
62+
canEditRowAtIndexPathProvider: canEditRowAtIndexPathProvider,
63+
canMoveRowAtIndexPathProvider: canMoveRowAtIndexPathProvider,
64+
sectionIndexTitles: sectionIndexTitles,
65+
sectionForSectionIndexTitle: sectionForSectionIndexTitle)
66+
defaultRowAnimation = animation
67+
}
68+
#else
69+
public init(
70+
tableView: UITableView,
71+
animation: UITableView.RowAnimation = .automatic,
72+
decideViewTransition: @escaping DecideViewTransition = { _, _, _ in .animated },
73+
configureCell: @escaping ConfigureCell,
74+
titleForHeaderInSectionProvider: @escaping TitleForHeaderInSectionProvider = { _, _ in nil },
75+
titleForFooterInSectionProvider: @escaping TitleForFooterInSectionProvider = { _, _ in nil },
76+
canEditRowAtIndexPathProvider: @escaping CanEditRowAtIndexPathProvider = { _, _ in true },
77+
canMoveRowAtIndexPathProvider: @escaping CanMoveRowAtIndexPathProvider = { _, _ in true }
78+
) {
79+
self.decideViewTransition = decideViewTransition
80+
super.init(tableView: tableView,
81+
configureCell: configureCell,
82+
titleForHeaderInSectionProvider: titleForHeaderInSectionProvider,
83+
titleForFooterInSectionProvider: titleForFooterInSectionProvider,
84+
canEditRowAtIndexPathProvider: canEditRowAtIndexPathProvider,
85+
canMoveRowAtIndexPathProvider: canMoveRowAtIndexPathProvider)
86+
defaultRowAnimation = animation
87+
}
88+
#endif
89+
90+
open func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<[Section]>) {
91+
Binder(self) { dataSource, sections in
92+
var snapshot = NSDiffableDataSourceSnapshot<Section, Section.Item>()
93+
snapshot.appendSections(sections)
94+
sections.forEach { section in
95+
snapshot.appendItems(section.items, toSection: section)
96+
}
97+
let animated = dataSource.decideViewTransition(dataSource, tableView, snapshot) == .animated
98+
dataSource.apply(snapshot, animatingDifferences: animated)
99+
}.on(observedEvent)
100+
}
101+
}
102+
103+
#endif

0 commit comments

Comments
 (0)