Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Example/ReactiveDataDisplayManager/Collection.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<segue destination="EB6-CQ-xkp" kind="show" identifier="refreshingCollection" id="RIR-tI-dAc"/>
<segue destination="dcC-YA-wFj" kind="show" identifier="twoDirectionPaginatableCollection" id="EKN-aB-kFO"/>
<segue destination="WXf-rM-5r7" kind="show" identifier="stackCellCollectionViewController" id="ljM-KS-ixh"/>
<segue destination="hKO-Fp-O7f" kind="show" identifier="horizontalTwoDirectionPaginatableCollection" id="HOF-Cl-xHy"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="4hK-7m-aH1" userLabel="First Responder" sceneMemberID="firstResponder"/>
Expand Down Expand Up @@ -882,6 +883,50 @@
</objects>
<point key="canvasLocation" x="-719" y="2248"/>
</scene>
<!--Horizontal Two Direction Paginatable Collection View Controller-->
<scene sceneID="XXX-DU-TWm">
<objects>
<viewController id="hKO-Fp-O7f" customClass="HorizontalTwoDirectionPaginatableCollectionViewController" customModule="ReactiveDataDisplayManagerExample_iOS" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="ZaG-oG-SJ4">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="cxu-Ea-CM2">
<rect key="frame" x="0.0" y="92" width="414" height="721"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="ts6-eB-wfW">
<size key="itemSize" width="200" height="44"/>
<size key="estimatedItemSize" width="200" height="44"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
</collectionView>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" style="medium" id="zm3-wO-NvS">
<rect key="frame" x="0.0" y="87" width="414" height="721"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</activityIndicatorView>
</subviews>
<viewLayoutGuide key="safeArea" id="JA2-N8-uyI"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="cxu-Ea-CM2" firstAttribute="leading" secondItem="JA2-N8-uyI" secondAttribute="leading" id="6kJ-6j-osI"/>
<constraint firstItem="cxu-Ea-CM2" firstAttribute="top" secondItem="JA2-N8-uyI" secondAttribute="top" id="N6v-T2-rir"/>
<constraint firstItem="JA2-N8-uyI" firstAttribute="bottom" secondItem="cxu-Ea-CM2" secondAttribute="bottom" id="vAG-C2-xWy"/>
<constraint firstItem="JA2-N8-uyI" firstAttribute="trailing" secondItem="cxu-Ea-CM2" secondAttribute="trailing" id="ynm-EV-4bv"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="Fi2-lE-HDS"/>
<connections>
<outlet property="activityIndicator" destination="zm3-wO-NvS" id="WYz-NN-qBg"/>
<outlet property="collectionView" destination="cxu-Ea-CM2" id="bll-ee-4dw"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="MkR-Rn-Nbi" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="5842" y="3025"/>
</scene>
</scenes>
<resources>
<image name="table.fill" catalog="system" width="128" height="93"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ final class MainCollectionViewController: UIViewController {
case alignedCollection
case dynamicHeightViewController
case twoDirectionPaginatableCollection
case horizontalTwoDirectionPaginatableCollection
}

// MARK: - Constants
Expand All @@ -53,6 +54,7 @@ final class MainCollectionViewController: UIViewController {
("Collection with diffableDataSource", .diffableCollection),
("Collection with pagination", .paginatableCollection),
("Collection with two direction pagination", .twoDirectionPaginatableCollection),
("Collection with two direction horizontal pagination", .horizontalTwoDirectionPaginatableCollection),
("Collection with compositional layout", .compositionalCollection),
("Collection with DifferenceKit", .differenceCollection),
("List Appearances with swipeable items", .swipeableListAppearances),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class PaginatableCollectionViewController: UIViewController {
private lazy var progressView = PaginatorView(frame: .init(x: 0, y: 0, width: collectionView.frame.width, height: 80))

private lazy var adapter = collectionView.rddm.baseBuilder
.add(plugin: .paginatable(progressView: progressView, output: self))
.add(plugin: .bottomPaginatable(progressView: progressView, output: self))
.build()

private weak var paginatableInput: PaginatableInput?
Expand Down Expand Up @@ -72,8 +72,8 @@ private extension PaginatableCollectionViewController {
activityIndicator.startAnimating()

// hide footer
paginatableInput?.updatePagination(canIterate: false)
paginatableInput?.updateProgress(isLoading: false)
paginatableInput?.updatePaginationEnabled(false, at: .forward(.bottom))
paginatableInput?.updatePaginationState(.idle, at: .forward(.bottom))

// imitation of loading first page
delay(.now() + .seconds(3)) { [weak self] in
Expand All @@ -84,7 +84,7 @@ private extension PaginatableCollectionViewController {
self?.activityIndicator?.stopAnimating()

// show footer
self?.paginatableInput?.updatePagination(canIterate: true)
self?.paginatableInput?.updatePaginationEnabled(true, at: .forward(.bottom))
}
}

Expand Down Expand Up @@ -137,24 +137,23 @@ private extension PaginatableCollectionViewController {

extension PaginatableCollectionViewController: PaginatableOutput {

func onPaginationInitialized(with input: PaginatableInput) {
func onPaginationInitialized(with input: PaginatableInput, at direction: PagingDirection) {
paginatableInput = input
}

func loadNextPage(with input: PaginatableInput) {
func loadNextPage(with input: PaginatableInput, at direction: PagingDirection) {

input.updateProgress(isLoading: true)
input.updatePaginationState(.loading, at: direction)

delay(.now() + .seconds(3)) { [weak self, weak input] in
let canFillNext = self?.canFillNext() ?? false
if canFillNext {
let canIterate = self?.fillNext() ?? false

input?.updateProgress(isLoading: false)
input?.updatePagination(canIterate: canIterate)
input?.updatePaginationState(.idle, at: direction)
input?.updatePaginationEnabled(canIterate, at: direction)
} else {
input?.updateProgress(isLoading: false)
input?.updateError(SampleError.sample)
input?.updatePaginationState(.error(SampleError.sample), at: direction)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
//
// HorizontalTwoDirectionPaginatableCollectionViewController.swift
// ReactiveDataDisplayManagerExample_iOS
//
// Created by Konstantin Porokhov on 30.08.2023.
//

import UIKit
import ReactiveDataDisplayManager
import ReactiveDataComponents

final class HorizontalTwoDirectionPaginatableCollectionViewController: UIViewController {

// MARK: - Nested types

private enum ScrollDirection {
case left
case right
}

// MARK: - Constants

private enum Constants {
static let maxPagesCount = 5
static let pageSize = 20
static let paginatorHeight: CGFloat = 80
static let firstPageMiddleIndexPath = IndexPath(row: Constants.pageSize / 2, section: 0)
}

// MARK: - IBOutlet

@IBOutlet private weak var collectionView: UICollectionView!
@IBOutlet private weak var activityIndicator: UIActivityIndicatorView!

// MARK: - Private Properties

private lazy var leftProgressView = PaginatorView(frame: .init(x: 0,
y: 0,
width: 200,
height: collectionView.frame.height))
private lazy var rightProgressView = PaginatorView(frame: .init(x: 0,
y: 0,
width: 200,
height: collectionView.frame.height))

private lazy var adapter = collectionView.rddm.baseBuilder
.add(plugin: .leftPaginatable(progressView: leftProgressView, output: self))
.add(plugin: .rightPaginatable(progressView: rightProgressView, output: self))
.build()

private weak var bottomPaginatableInput: PaginatableInput?
private weak var topPaginatableInput: PaginatableInput?

private var isFirstPageLoading = true
private var currentLeftPage = 0
private var currentRightPage = 0

private lazy var emptyCell = CollectionSpacerCell.rddm.baseGenerator(with: CollectionSpacerCell.Model(height: 0), and: .class)

// MARK: - UIViewController

override func viewDidLoad() {
super.viewDidLoad()
title = "Collection with two direction pagination"

configureActivityIndicatorIfNeeded()
configureCollectionView()
loadFirstPage()
}

}

// MARK: - Configuration

private extension HorizontalTwoDirectionPaginatableCollectionViewController {

func configureActivityIndicatorIfNeeded() {
if #available(iOS 13.0, tvOS 13.0, *) {
activityIndicator.style = .medium
}
}

}

// MARK: - Private Methods

private extension HorizontalTwoDirectionPaginatableCollectionViewController {

func configureCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = .init(width: 300, height: 200)
collectionView.setCollectionViewLayout(layout, animated: true)
}

func loadFirstPage() {

// show loader
activityIndicator.isHidden = false
activityIndicator.hidesWhenStopped = true
activityIndicator.startAnimating()

// hide footer
bottomPaginatableInput?.updatePaginationEnabled(false, at: .forward(.bottom))
topPaginatableInput?.updatePaginationEnabled(false, at: .backward(.top))
bottomPaginatableInput?.updatePaginationState(.idle, at: .forward(.bottom))
topPaginatableInput?.updatePaginationState(.loading, at: .backward(.top))

// imitation of loading first page
delay(.now() + .seconds(3)) { [weak self] in
// fill table
self?.fillAdapter()

// hide loader
self?.activityIndicator?.stopAnimating()

// scroll to middle
self?.collectionView.scrollToItem(at: Constants.firstPageMiddleIndexPath, at: .centeredVertically, animated: false)

// show pagination loader if update is needed
self?.bottomPaginatableInput?.updatePaginationEnabled(true, at: .forward(.bottom))
self?.topPaginatableInput?.updatePaginationEnabled(true, at: .backward(.top))
}
}

/// This method is used to fill adapter
func fillAdapter() {
adapter += emptyCell

for _ in 0...Constants.pageSize {
adapter += makeGenerator()
}

adapter => .reload
}

private func makeGenerator(for scrollDirection: ScrollDirection? = nil) -> CollectionCellGenerator {
var currentPage = 0
if let scrollDirection = scrollDirection {
switch scrollDirection {
case .left:
currentPage = currentLeftPage
case .right:
currentPage = currentRightPage
}
}

let title = "Random cell \(Int.random(in: 0...1000)) from page \(currentPage)"
return TitleCollectionViewCell.rddm.baseGenerator(with: title)
}

func canFillPages() -> Bool {
if isFirstPageLoading {
isFirstPageLoading.toggle()
return false
} else {
return true
}
}

func fillNext() -> Bool {
currentRightPage += 1

var newGenerators = [CollectionCellGenerator]()

for _ in 0...Constants.pageSize {
newGenerators.append(makeGenerator(for: .right))
}

if let lastGenerator = adapter.sections.last?.generators.last {
adapter.insert(after: lastGenerator, new: newGenerators)
} else {
adapter += newGenerators
adapter => .reload
}

return currentRightPage != Constants.maxPagesCount
}

func fillPrev() -> Bool {
currentLeftPage -= 1

let newGenerators = (0...Constants.pageSize).map { _ in
return makeGenerator(for: .left)
}
adapter.insert(after: emptyCell, new: newGenerators, with: nil)

return abs(currentLeftPage) != Constants.maxPagesCount
}

}

// MARK: - PaginatableOutput

extension HorizontalTwoDirectionPaginatableCollectionViewController: PaginatableOutput {

func onPaginationInitialized(with input: PaginatableInput, at direction: PagingDirection) {
switch direction {
case .backward:
topPaginatableInput = input
case .forward:
bottomPaginatableInput = input
}
}

func loadNextPage(with input: PaginatableInput, at direction: PagingDirection) {

input.updatePaginationState(.loading, at: direction)

delay(.now() + .seconds(3)) { [weak self, weak input] in
let canFillNext = self?.canFillPages() ?? false
if canFillNext {
let canIterate: Bool
switch direction {
case .backward:
canIterate = self?.fillPrev() ?? false
case .forward:
canIterate = self?.fillNext() ?? false
}

input?.updatePaginationState(.idle, at: direction)
input?.updatePaginationEnabled(canIterate, at: direction)
} else {
input?.updatePaginationState(.error(SampleError.sample), at: direction)
}
}
}

}
Loading