Skip to content

Commit

Permalink
Add method for reload data without passing initial item
Browse files Browse the repository at this point in the history
When using PagingViewControllerDataSource we can reload data without
knowing the initial item, as we can just select the previously
selected item or the first in the list. This makes it much easier to
reload data in most use cases.
  • Loading branch information
rechsteiner committed Mar 25, 2018
1 parent 07641e2 commit 8ebd931
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 12 deletions.
3 changes: 1 addition & 2 deletions IconsExample/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ struct IconItem: PagingItem, Hashable, Comparable {
static func ==(lhs: IconItem, rhs: IconItem) -> Bool {
return (
lhs.index == rhs.index &&
lhs.icon == rhs.icon &&
lhs.image == rhs.image
lhs.icon == rhs.icon
)
}
}
Expand Down
7 changes: 6 additions & 1 deletion Parchment/Classes/EMPageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,12 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate {
}

}


open func removeAllViewControllers() {
self.removeChildIfNeeded(beforeViewController)
self.removeChildIfNeeded(selectedViewController)
self.removeChildIfNeeded(afterViewController)
}

/**
Transitions to the view controller right of the currently selected view controller in a horizontal orientation, or below the currently selected view controller in a vertical orientation. Also described as going to the next page.
Expand Down
7 changes: 7 additions & 0 deletions Parchment/Classes/PagingStateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class PagingStateMachine<T: PagingItem> where T: Equatable {
handleTransitionSizeEvent(event)
case .cancelScrolling:
handleCancelScrollingEvent(event)
case .removeAll:
handleRemoveAllEvent(event)
}
}

Expand Down Expand Up @@ -179,4 +181,9 @@ class PagingStateMachine<T: PagingItem> where T: Equatable {
}
}

private func handleRemoveAllEvent(_ event: PagingEvent<T>) {
let oldState = state
state = .empty
onStateChange?(oldState, state, event)
}
}
33 changes: 31 additions & 2 deletions Parchment/Classes/PagingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,32 @@ open class PagingViewController<T: PagingItem>:

// MARK: Public Methods

/// Reload data for all the menu items. This will keep the
/// previously selected item if it's still part of the updated data.
/// If not, it will select the first item in the list. This method
/// will not work when using PagingViewControllerInfiniteDataSource
/// as we then need to know what the initial item should be. You
/// should use the reloadData(around:) method in that case.
open func reloadData() {
let previouslySelected = state.currentPagingItem
let items = generateItemsForIndexedDataSource()
indexedDataSource?.items = items

if let pagingItem = items.first(where: { $0 == previouslySelected }) {
select(pagingItem: pagingItem, animated: false)
} else if let firstItem = items.first {
select(pagingItem: firstItem, animated: false)
} else {
stateMachine.fire(.removeAll)
}
}

/// Reload data around given paging item. This will set the given
/// paging item as selected and generate new items around it. This
/// will also reload the view controllers displayed in the page view
/// controller.
/// controller. You need to use this method to reload data when
/// using PagingViewControllerInfiniteDataSource as we need to know
/// the initial item.
///
/// - Parameter pagingItem: The `PagingItem` that will be selected
/// after the data reloads.
Expand Down Expand Up @@ -640,7 +662,7 @@ open class PagingViewController<T: PagingItem>:

collectionViewLayout.invalidateLayout(with: invalidationContext)
case .empty:
break
removeAll()
}
}

Expand Down Expand Up @@ -695,6 +717,13 @@ open class PagingViewController<T: PagingItem>:
return items
}

private func removeAll() {
visibleItems = PagingItems(items: [])
collectionViewLayout.visibleItems = visibleItems
pageViewController.removeAllViewControllers()
collectionView.reloadData()
}

private func reloadItems(around pagingItem: T, keepExisting: Bool = false) {
var toItems = generateItems(around: pagingItem)

Expand Down
1 change: 1 addition & 0 deletions Parchment/Enums/PagingEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum PagingEvent<T: PagingItem> where T: Equatable {
case transitionSize
case cancelScrolling
case reload(contentOffset: CGPoint)
case removeAll
}

extension PagingEvent {
Expand Down
106 changes: 101 additions & 5 deletions ParchmentTests/PagingViewControllerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,108 @@ class DeinitFixedPagingViewController: FixedPagingViewController {
deinit { deinitCalled?() }
}

class ReloadingDataSource: PagingViewControllerDataSource {
var items: [PagingIndexItem] = []

func numberOfViewControllers<T>(in pagingViewController: PagingViewController<T>) -> Int {
return items.count
}

func pagingViewController<T>(_ pagingViewController: PagingViewController<T>, viewControllerForIndex index: Int) -> UIViewController {
return UIViewController()
}

func pagingViewController<T>(_ pagingViewController: PagingViewController<T>, pagingItemForIndex index: Int) -> T {
return items[index] as! T
}
}

class PagingViewControllerSpec: QuickSpec {

override func spec() {

xdescribe("PagingViewController") {
describe("PagingViewController") {

describe("reloading data") {

let dataSource = ReloadingDataSource()
var viewController: PagingViewController<PagingIndexItem>!

beforeEach {
dataSource.items = [
PagingIndexItem(index: 0, title: "First"),
PagingIndexItem(index: 1, title: "Second")
]

viewController = PagingViewController()
viewController.menuItemSize = .fixed(width: 100, height: 50)
viewController.dataSource = dataSource

UIApplication.shared.keyWindow!.rootViewController = viewController
let _ = viewController.view

viewController.collectionView.bounds = CGRect(x: 0, y: 0, width: 1000, height: 50)
viewController.viewDidLayoutSubviews()
}

it("reloads data around item") {
let first = PagingIndexItem(index: 2, title: "Third")
let third = PagingIndexItem(index: 3, title: "Fourth")
dataSource.items = [first, third]
viewController.reloadData(around: first)

let cell1 = viewController.collectionView.cellForItem(at: IndexPath(item: 0, section: 0)) as! PagingTitleCell
let cell2 = viewController.collectionView.cellForItem(at: IndexPath(item: 1, section: 0)) as! PagingTitleCell

expect(cell1.titleLabel.text).to(equal("Third"))
expect(cell2.titleLabel.text).to(equal("Fourth"))
}

it("selects previously selected item when reloading data") {
let first = PagingIndexItem(index: 0, title: "First")
let second = PagingIndexItem(index: 1, title: "Second")
let third = PagingIndexItem(index: 2, title: "Third")

viewController.select(index: 1)
dataSource.items = [first, second, third]
viewController.reloadData()

let cell1 = viewController.collectionView.cellForItem(at: IndexPath(item: 0, section: 0)) as! PagingTitleCell
let cell2 = viewController.collectionView.cellForItem(at: IndexPath(item: 1, section: 0)) as! PagingTitleCell
let cell3 = viewController.collectionView.cellForItem(at: IndexPath(item: 2, section: 0)) as! PagingTitleCell

expect(cell1.titleLabel.text).to(equal("First"))
expect(cell2.titleLabel.text).to(equal("Second"))
expect(cell3.titleLabel.text).to(equal("Third"))
expect(viewController.state).to(equal(PagingState.selected(pagingItem: second)))
}

it("selects the first item when reloading data with all new items") {
let third = PagingIndexItem(index: 2, title: "Third")
let fourth = PagingIndexItem(index: 3, title: "Fourth")

viewController.select(index: 1)
dataSource.items = [third, fourth]
viewController.reloadData()

let cell1 = viewController.collectionView.cellForItem(at: IndexPath(item: 0, section: 0)) as! PagingTitleCell
let cell2 = viewController.collectionView.cellForItem(at: IndexPath(item: 1, section: 0)) as! PagingTitleCell

expect(cell1.titleLabel.text).to(equal("Third"))
expect(cell2.titleLabel.text).to(equal("Fourth"))
expect(viewController.state).to(equal(PagingState.selected(pagingItem: third)))
}

fit("display an empty view after reloading data with no items") {
dataSource.items = []
viewController.reloadData()

expect(viewController.pageViewController.scrollView.subviews).to(beEmpty())
expect(viewController.collectionView.numberOfItems(inSection: 0)).to(equal(0))
}
}

describe("reloading items") {
describe("selecting items") {

let dataSource = DataSource()
var viewController: PagingViewController<Item>!
Expand All @@ -60,21 +155,22 @@ class PagingViewControllerSpec: QuickSpec {
let _ = viewController.view

viewController.collectionView.bounds = CGRect(x: 0, y: 0, width: 1000, height: 50)
viewController.viewDidLayoutSubviews()
}

it("reloadItems: at begining") {
it("selecting the first item generates enough items") {
viewController.select(pagingItem: Item(index: 0))
let items = viewController.collectionView.numberOfItems(inSection: 0)
expect(items).to(equal(21))
}

it("reloadItems: at center") {
it("selecting the center item generates enough items") {
viewController.select(pagingItem: Item(index: 20))
let items = viewController.collectionView.numberOfItems(inSection: 0)
expect(items).to(equal(21))
}

it("reloadItems: at end") {
it("selecting the last item generates enough items") {
viewController.select(pagingItem: Item(index: 50))
let items = viewController.collectionView.numberOfItems(inSection: 0)
expect(items).to(equal(21))
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,26 @@ func select(index: Int, animated: Bool = false)

## Reload data

You can reload data for a given `PagingItem`:
You can reload data using this method:

```Swift
func reloadData()
```

This will keep the previously selected item if it's still part of the
updated data. If not, it will select the first item in the list. It
will also reload the view controllers displayed in the page view
controller.

Calling `reloadData()` will not work when using
`PagingViewControllerInfiniteDataSource`, as we then need to know what
the initial item should be. In that case you should use this method:

```Swift
func reloadData(around: PagingItem)
```

This will mark the given paging item as selected and generate new items around it. It will also reload the view controllers displayed in the page view controller.
This will mark the given paging item as selected and generate new items around it.

## Delegate

Expand Down

0 comments on commit 8ebd931

Please sign in to comment.