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 bbfea38 commit 508fa8b
Show file tree
Hide file tree
Showing 6 changed files with 488 additions and 0 deletions.
51 changes: 51 additions & 0 deletions Sources/Differentiator/DiffableSectionModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// DiffableSectionModel.swift
//
//
// Created by mlch911 on 2023/5/30.
//

import Foundation

public struct DiffableSectionModel<Section: Hashable, ItemType: Hashable> {
public var model: Section
public var items: [Item]

public init(model: Section, items: [Item]) {
self.model = model
self.items = items
}
}

extension DiffableSectionModel: DiffableSectionModelType {
public typealias Identity = Section
public typealias Item = ItemType

public var identity: Section {
return model
}
}

extension DiffableSectionModel
: CustomStringConvertible {

public var description: String {
return "\(self.model) > \(items)"
}
}

extension DiffableSectionModel {
public init(original: DiffableSectionModel<Section, Item>, items: [Item]) {
self.model = original.model
self.items = items
}
}

extension DiffableSectionModel
: Equatable {

public static func == (lhs: DiffableSectionModel, rhs: DiffableSectionModel) -> Bool {
return lhs.model == rhs.model
&& lhs.items == rhs.items
}
}
10 changes: 10 additions & 0 deletions Sources/Differentiator/DiffableSectionModelType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// DiffableSectionModelType.swift
//
//
// Created by mlch911 on 2023/5/30.
//

import Foundation

public protocol DiffableSectionModelType: SectionModelType, Hashable where Item: Hashable {}
100 changes: 100 additions & 0 deletions Sources/RxDataSources/CollectionViewDiffableDataSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// CollectionViewDiffableDataSource.swift
//
//
// Created by mlch911 on 2023/5/30.
//

#if os(iOS) || os(tvOS)
import Foundation
import UIKit
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif

@available(iOS 13.0, tvOS 13.0, *)
open class CollectionViewDiffableDataSource<Section: DiffableSectionModelType>:
UICollectionViewDiffableDataSource<Section, Section.Item> {

public enum Error: Swift.Error {
case outOfBounds(indexPath: IndexPath)
}

public typealias ConfigureCellProvider = (_ dataSource: CollectionViewDiffableDataSource<Section>, _ collectionView: UICollectionView, _ indexPath: IndexPath, Section.Item) -> UICollectionViewCell
public typealias ConfigureSupplementaryViewProvider = (_ dataSource: CollectionViewDiffableDataSource<Section>, _ collectionView: UICollectionView, _ elementKind: String, _ indexPath: IndexPath) -> UICollectionReusableView?
public typealias MoveItemProvider = (_ dataSource: CollectionViewDiffableDataSource<Section>, _ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void
public typealias CanMoveItemAtIndexPathProvider = (_ dataSource: CollectionViewDiffableDataSource<Section>, _ indexPath: IndexPath) -> Bool

open var configureSupplementaryView: ConfigureSupplementaryViewProvider?
open var canMoveItemAtIndexPath: CanMoveItemAtIndexPathProvider?
open var moveItem: MoveItemProvider?

public init(
collectionView: UICollectionView,
configureCell: @escaping ConfigureCellProvider,
configureSupplementaryView: @escaping ConfigureSupplementaryViewProvider = { _, _, _, _ in nil },
moveItem: @escaping MoveItemProvider = { _, _, _ in () },
canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPathProvider = { _, _ in true }
) {
self.moveItem = moveItem
self.canMoveItemAtIndexPath = canMoveItemAtIndexPath
self.configureSupplementaryView = configureSupplementaryView
weak var dataSource: CollectionViewDiffableDataSource<Section>!
super.init(collectionView: collectionView, cellProvider: {
configureCell(dataSource, $0, $1, $2)
})
self.supplementaryViewProvider = { [unowned self] in
self.configureSupplementaryView?(self, $0, $1, $2)
}
dataSource = self
}

// MARK: - UICollectionViewDataSource
open override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
canMoveItemAtIndexPath?(self, indexPath) ?? false
}

open override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
self.moveItem?(self, sourceIndexPath, destinationIndexPath)
}
}

extension CollectionViewDiffableDataSource: SectionedViewDataSourceType {
open func model(at indexPath: IndexPath) throws -> Any {
guard let item = itemIdentifier(for: indexPath) else { throw Error.outOfBounds(indexPath: indexPath) }
return item
}

open func sectionModel(at index: Int) -> Section? {
if #available(iOS 15.0, tvOS 15.0, *) {
return sectionIdentifier(for: index)
}
let sections = snapshot().sectionIdentifiers
guard index >= 0 && index < sections.count else { return nil }
return sections[index]
}

open subscript(section: Int) -> Section? {
sectionModel(at: section)
}

open subscript(indexPath: IndexPath) -> Section.Item? {
itemIdentifier(for: indexPath)
}
}

public extension CollectionViewDiffableDataSource {
func numberOfSections() -> Int {
snapshot().numberOfSections
}

func numberOfItems(in section: Int) -> Int {
let snapshot = snapshot()
guard section >= 0, section < snapshot.sectionIdentifiers.count else { return 0 }
return snapshot.numberOfItems(inSection: snapshot.sectionIdentifiers[section])
}
}

#endif

73 changes: 73 additions & 0 deletions Sources/RxDataSources/RxCollectionViewDiffableDataSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// RxCollectionViewDiffableDataSource.swift
//
//
// Created by mlch911 on 2023/5/30.
//

#if os(iOS) || os(tvOS)
import Foundation
import UIKit
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif

@available(iOS 13.0, tvOS 13.0, *)
open class RxCollectionViewDiffableDataSource<Section: DiffableSectionModelType>
: CollectionViewDiffableDataSource<Section>
, RxCollectionViewDataSourceType {
public typealias Element = [Section]

open func collectionView(_ collectionView: UICollectionView, observedEvent: RxSwift.Event<[Section]>) {
Binder(self) { dataSource, sections in
var snapshot = NSDiffableDataSourceSnapshot<Section, Section.Item>()
snapshot.appendSections(sections)
sections.forEach { section in
snapshot.appendItems(section.items, toSection: section)
}
dataSource.apply(snapshot, animatingDifferences: false)
}.on(observedEvent)
}
}

@available(iOS 13.0, tvOS 13.0, *)
open class RxCollectionViewAnimatedDiffableDataSource<Section: DiffableSectionModelType>
: CollectionViewDiffableDataSource<Section>
, RxCollectionViewDataSourceType {
public typealias Element = [Section]
public typealias DecideViewTransition = (CollectionViewDiffableDataSource<Section>, UICollectionView, NSDiffableDataSourceSnapshot<Section, Section.Item>) -> ViewTransition

/// Calculates view transition depending on type of changes
open var decideViewTransition: DecideViewTransition

public init(
collectionView: UICollectionView,
decideViewTransition: @escaping DecideViewTransition = { _, _, _ in .animated },
configureCell: @escaping ConfigureCellProvider,
configureSupplementaryView: @escaping ConfigureSupplementaryViewProvider = { _, _, _, _ in nil },
moveItem: @escaping MoveItemProvider = { _, _, _ in () },
canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPathProvider = { _, _ in true }
) {
self.decideViewTransition = decideViewTransition
super.init(collectionView: collectionView,
configureCell: configureCell,
configureSupplementaryView: configureSupplementaryView,
moveItem: moveItem,
canMoveItemAtIndexPath: canMoveItemAtIndexPath)
}

open func collectionView(_ collectionView: UICollectionView, observedEvent: RxSwift.Event<[Section]>) {
Binder(self) { dataSource, sections in
var snapshot = NSDiffableDataSourceSnapshot<Section, Section.Item>()
snapshot.appendSections(sections)
sections.forEach { section in
snapshot.appendItems(section.items, toSection: section)
}
let animated = dataSource.decideViewTransition(dataSource, collectionView, snapshot) == .animated
dataSource.apply(snapshot, animatingDifferences: animated)
}.on(observedEvent)
}
}

#endif
103 changes: 103 additions & 0 deletions Sources/RxDataSources/RxTableViewDiffableDataSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// RxTableViewDiffableDataSource.swift
//
//
// Created by mlch911 on 2023/5/30.
//

#if os(iOS) || os(tvOS)
import Foundation
import UIKit
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif

@available(iOS 13.0, tvOS 13.0, *)
open class RxTableViewDiffableDataSource<Section: DiffableSectionModelType>
: TableViewDiffableDataSource<Section>
, RxTableViewDataSourceType {
public typealias Element = [Section]

open func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<[Section]>) {
Binder(self) { dataSource, sections in
var snapshot = NSDiffableDataSourceSnapshot<Section, Section.Item>()
snapshot.appendSections(sections)
sections.forEach { section in
snapshot.appendItems(section.items, toSection: section)
}
dataSource.apply(snapshot, animatingDifferences: false)
}.on(observedEvent)
}
}

@available(iOS 13.0, tvOS 13.0, *)
open class RxTableViewAnimatedDiffableDataSource<Section: DiffableSectionModelType>
: TableViewDiffableDataSource<Section>
, RxTableViewDataSourceType {
public typealias Element = [Section]
public typealias DecideViewTransition = (TableViewDiffableDataSource<Section>, UITableView, NSDiffableDataSourceSnapshot<Section, Section.Item>) -> ViewTransition

/// Calculates view transition depending on type of changes
public var decideViewTransition: DecideViewTransition

#if os(iOS)
public init(
tableView: UITableView,
animation: UITableView.RowAnimation = .automatic,
decideViewTransition: @escaping DecideViewTransition = { _, _, _ in .animated },
configureCell: @escaping ConfigureCell,
titleForHeaderInSectionProvider: @escaping TitleForHeaderInSectionProvider = { _, _ in nil },
titleForFooterInSectionProvider: @escaping TitleForFooterInSectionProvider = { _, _ in nil },
canEditRowAtIndexPathProvider: @escaping CanEditRowAtIndexPathProvider = { _, _ in true },
canMoveRowAtIndexPathProvider: @escaping CanMoveRowAtIndexPathProvider = { _, _ in true },
sectionIndexTitles: @escaping SectionIndexTitlesProvider = { _ in nil },
sectionForSectionIndexTitle: @escaping SectionForSectionIndexTitleProvider = { _, _, index in index }
) {
self.decideViewTransition = decideViewTransition
super.init(tableView: tableView,
configureCell: configureCell,
titleForHeaderInSectionProvider: titleForHeaderInSectionProvider,
titleForFooterInSectionProvider: titleForFooterInSectionProvider,
canEditRowAtIndexPathProvider: canEditRowAtIndexPathProvider,
canMoveRowAtIndexPathProvider: canMoveRowAtIndexPathProvider,
sectionIndexTitles: sectionIndexTitles,
sectionForSectionIndexTitle: sectionForSectionIndexTitle)
defaultRowAnimation = animation
}
#else
public init(
tableView: UITableView,
animation: UITableView.RowAnimation = .automatic,
decideViewTransition: @escaping DecideViewTransition = { _, _, _ in .animated },
configureCell: @escaping ConfigureCell,
titleForHeaderInSectionProvider: @escaping TitleForHeaderInSectionProvider = { _, _ in nil },
titleForFooterInSectionProvider: @escaping TitleForFooterInSectionProvider = { _, _ in nil },
canEditRowAtIndexPathProvider: @escaping CanEditRowAtIndexPathProvider = { _, _ in true },
canMoveRowAtIndexPathProvider: @escaping CanMoveRowAtIndexPathProvider = { _, _ in true }
) {
self.decideViewTransition = decideViewTransition
super.init(tableView: tableView,
configureCell: configureCell,
titleForHeaderInSectionProvider: titleForHeaderInSectionProvider,
titleForFooterInSectionProvider: titleForFooterInSectionProvider,
canEditRowAtIndexPathProvider: canEditRowAtIndexPathProvider,
canMoveRowAtIndexPathProvider: canMoveRowAtIndexPathProvider)
defaultRowAnimation = animation
}
#endif

open func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<[Section]>) {
Binder(self) { dataSource, sections in
var snapshot = NSDiffableDataSourceSnapshot<Section, Section.Item>()
snapshot.appendSections(sections)
sections.forEach { section in
snapshot.appendItems(section.items, toSection: section)
}
let animated = dataSource.decideViewTransition(dataSource, tableView, snapshot) == .animated
dataSource.apply(snapshot, animatingDifferences: animated)
}.on(observedEvent)
}
}

#endif
Loading

0 comments on commit 508fa8b

Please sign in to comment.