diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a4da9c..099eb0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ NEXT - Reverted back to Swift 5 language mode because of issues in UIKit. ([@jessesquires](https://github.com/jessesquires), [#116](https://github.com/jessesquires/ReactiveCollectionsKit/pull/116)) - Applying a snapshot using `reloadData` now always occurs on the main thread. ([@jessesquires](https://github.com/jessesquires), [#116](https://github.com/jessesquires/ReactiveCollectionsKit/pull/116)) - Implemented additional selection APIs for `CellViewModel`: `shouldSelect`, `shouldDeselect`, `didDeselect()`. ([@nuomi1](https://github.com/nuomi1), [#127](https://github.com/jessesquires/ReactiveCollectionsKit/pull/127)) +- Allow setting a `UIScrollViewDelegate` object to receive scroll view events from the collection view. ([@ruddfawcett](https://github.com/ruddfawcett), [#131](https://github.com/jessesquires/ReactiveCollectionsKit/pull/131), [#133](https://github.com/jessesquires/ReactiveCollectionsKit/pull/133)) 0.1.6 ----- diff --git a/Example/Sources/List/ListViewController.swift b/Example/Sources/List/ListViewController.swift index a0fbc92..76e1acc 100644 --- a/Example/Sources/List/ListViewController.swift +++ b/Example/Sources/List/ListViewController.swift @@ -17,12 +17,19 @@ import UIKit final class ListViewController: ExampleViewController, CellEventCoordinator { - lazy var driver = CollectionViewDriver( - view: self.collectionView, - options: .init(diffOnBackgroundQueue: true), - emptyViewProvider: sharedEmptyViewProvider, - cellEventCoordinator: self - ) + lazy var driver: CollectionViewDriver = { + let driver = CollectionViewDriver( + view: self.collectionView, + options: .init(diffOnBackgroundQueue: true), + emptyViewProvider: sharedEmptyViewProvider, + cellEventCoordinator: self + ) + + // Access `UIScrollViewDelegate` and handle protocol + driver.scrollViewDelegate = self + + return driver + }() override var model: Model { didSet { @@ -131,3 +138,11 @@ final class ListViewController: ExampleViewController, CellEventCoordinator { return CollectionViewModel(id: "list_view", sections: [peopleSection, colorSection]) } } + +extension ListViewController: UIScrollViewDelegate { + + // Demonstrate delegate override; tapping status bar does not scroll to top + func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { + false + } +} diff --git a/Sources/CollectionViewDriver.swift b/Sources/CollectionViewDriver.swift index 5d57ece..a27878c 100644 --- a/Sources/CollectionViewDriver.swift +++ b/Sources/CollectionViewDriver.swift @@ -31,6 +31,9 @@ public final class CollectionViewDriver: NSObject { /// The collection view model. @Published public private(set) var viewModel: CollectionViewModel + + /// The scroll view delegate to forward. + public weak var scrollViewDelegate: UIScrollViewDelegate? private let _emptyViewProvider: EmptyViewProvider? @@ -373,3 +376,96 @@ extension CollectionViewDriver: UICollectionViewDelegate { self.viewModel._safeSupplementaryViewModel(ofKind: elementKind, at: indexPath)?.didEndDisplaying() } } + +// MARK: UIScrollViewDelegate + +extension CollectionViewDriver: UIScrollViewDelegate { + // MARK: Managing offset and zoom scale changes + + /// :nodoc: + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.scrollViewDelegate?.scrollViewDidScroll?(scrollView) + } + + /// :nodoc: + public func scrollViewDidZoom(_ scrollView: UIScrollView) { + self.scrollViewDelegate?.scrollViewDidZoom?(scrollView) + } + + // MARK: Tracking dragging + + /// :nodoc: + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.scrollViewDelegate?.scrollViewWillBeginDragging?(scrollView) + } + + /// :nodoc: + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, + withVelocity velocity: CGPoint, + targetContentOffset: UnsafeMutablePointer) { + self.scrollViewDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset) + } + + /// :nodoc: + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, + willDecelerate decelerate: Bool) { + self.scrollViewDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) + } + + // MARK: Tracking deceleration and scrolling animation + + /// :nodoc: + public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { + self.scrollViewDelegate?.scrollViewWillBeginDragging?(scrollView) + } + + /// :nodoc: + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.scrollViewDelegate?.scrollViewDidEndDecelerating?(scrollView) + } + + /// :nodoc: + public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + self.scrollViewDelegate?.scrollViewDidEndScrollingAnimation?(scrollView) + } + + // MARK: Managing and tracking zooming more granularly + + /// :nodoc: + public func viewForZooming(in scrollView: UIScrollView) -> UIView? { + self.scrollViewDelegate?.viewForZooming?(in: scrollView) + } + + /// :nodoc: + public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, + with view: UIView?) { + self.scrollViewDelegate?.scrollViewWillBeginZooming?(scrollView, with: view) + } + + /// :nodoc: + public func scrollViewDidEndZooming(_ scrollView: UIScrollView, + with view: UIView?, + atScale scale: CGFloat) { + self.scrollViewDelegate?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale) + } + + // MARK: Managing if should scroll to top and tracking if done so + + /// :nodoc: + public func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { + self.scrollViewDelegate?.scrollViewShouldScrollToTop?(scrollView) ?? true + } + + /// :nodoc: + public func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { + self.scrollViewDelegate?.scrollViewDidScrollToTop?(scrollView) + } + + + // MARK: Tracking adjusted content insets on scroll view + + /// :nodoc: + public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { + self.scrollViewDelegate?.scrollViewDidChangeAdjustedContentInset?(scrollView) + } +}