SwiftPaginator

SwiftPaginator is a block based Swift class that handles pagination for you.

25
5
Swift

SwiftPaginator

CocoaPods
CocoaPods
CocoaPods
Travis branch
CocoaPods
Carthage compatible
🐧 Linux Ready

Paging

SwiftPaginator is a block-based Swift class that helps manage paginated resources.
Inspired by NMPaginator, an Obj-C class. SwiftPaginator leverages blocks and generics so that subclassing and delegates aren’t needed.

Features

  • [x] Written in Swift
  • [x] Uses Generics, No Subclassing required.
  • [x] Block based, no delegate required.
  • [x] 100% Test Coverage
  • [x] Fully Documented
  • [x] iOS | OSX | WatchOS | tvOS | Linux tested & ready
  • [x] Cocoapods installable
  • [x] Carthage installable
  • [x] Swift Package Manager installable

How to Install

Cocoapods (OS X)

Add this to your Podfile:

pod 'SwiftPaginator', '~> 1.0.0'

and run

$> pod install # (or update)

Carthage (OS X)

Add this to your Cartfile

github "apocolipse/SwiftPaginator" ~> 1.0.0

and run

$> carthage update # (bootstrap|build)

Swift Package Manager (OS X + Linux)

You can use The Swift Package Manager to
install SwiftPaginator by adding the proper description to your
Package.swift file:

import PackageDescription

let package = Package(
    name: "YOUR_PROJECT_NAME",
    targets: [],
    dependencies: [
        .Package(url: "https://github.com/apocolipse/SwiftPaginator.git", versions: "1.0.0" ..< Version.max)
    ]
)

Note that the Swift Package Manager is
still in early design and development, for more infomation checkout its
GitHub Page

Manually

Copy SwiftPaginator.swift to your project

How to use

Although based on NMPaginator, SwiftPaginator doesn’t require subclassing or delegates. The Paginator class uses Generics and Blocks to handle everything for you.

Set up a Paginator

import SwiftPaginator

let source = [["one", "two"], ["three", "four"]]

Simple example:

let stringPaginator = Paginator<String>(pageSize: 2, fetchHandler: { (paginator, page, pageSize) in
    paginator.receivedResults(source[page], total: 4)
}, resultsHandler: { (_, _) in
    self.tableView.reloadData()
})

A more complete example:

let stringPaginator = Paginator<String>(pageSize: 2, fetchHandler: {
      (paginator: Paginator, page: Int, pageSize: Int) in

      // implement how to fetch results, must call paginator.receivedResults(_:total:) or paginator.failed()
      if page < source.count {
        paginator.receivedResults(source[page], total: 4)
      } else {
        paginator.failed()
      }

    }, resultsHandler: { (paginator, results) in
        // Handle results
        print(results) // results for the given page
        print(paginator.results) // all results

    }, resetHandler: { (paginator) in
        // callback for a reset, Optional
        tableView.reloadData()
    }, failureHandler: { (paginator) in
        // callback for a failure, Optional
        self.presentFailureAlert()
    })
Setting up in a View Controller

Declare the property

class ViewController: UIViewController {
    var stringPaginator: Paginator<String>?
...

Be sure to call fetchFirstPage() in viewDidLoad(), use fetchNextPage() elsewhere when you need to load more results (i.e. when scrolling to the bottom of a scroll view or tapping a button)

override func viewDidLoad() {
    super.viewDidLoad()
    stringPaginator = ...
    stringPaginator.fetchFirstPage()
}

func loadMoreResults() {
    stringPaginator.fetchNextPage()
}

Fetch pages

Use fetchFirstPage() or fetchNextPage() to invoke a fetch. fetchFirstPage() calls reset() then fetchNextPage() internally.

stringPaginator.fetchNextPage()  // Use this one for most cases
stringPaginator.fetchFirstPage() // will reset paginator

Details on how to define fetch behavior below in fetchHandler
NOTE: Paginator will not allow simultaneous requests. Requests incoming while paginator.requestStatus is .InProgress will be ignored.

Reset the paginator

To reset the paginator and clear all stored results, simply call:

stringPaginator.reset()

Details on the resetHandler below show how to react to a reset()

Status

The requestStatus property stores an enum of type RequestStatus with 3 cases, .Done, .InProgress, .None. Until the 1st page is fetched, the status is .None, after which it will be .InProgress while async requests are processing and .Done otherwise.

Blocks Explained

All blocks have a paginator: Paginator<T> parameter, this is a reference to self called within the paginator so you may use it within the block without scope issues.
All blocks passed in the init method can be accessed and changed after initialization, i.e.

paginator.fetchHandler   = ...
paginator.resultsHandler = ...
paginator.resetHandler   = ...
paginator.failureHandler = ...

fetchHandler - Required

The fetchHandler block defines the behavior to fetch new pages. It is called internally from fetchNextPage().
NOTE: You Must call either paginator.receivedResults(_:total:) or paginator.failed() within the fetchHandler.

paginator.fetchHandler = {
    (paginator, page, pageSize) in

    APIClient.getResources() { (response, failure) in
        if failure {
            paginator.failed()
        } else {
            paginator.receivedResults(response.results, total: response.total)
        }
    }
}

resultsHandler - Required

The resultsHandler allows you to handle batches of new results coming in.
Although it is required to be defined, it can be empty, i.e.

...
resultsHandler: { (_, _) in },
...

But usually will be used to notify the View Controller to update the UI

...
resultsHandler: { (paginator, results) in
    self.handleNewResults(results)
    self.tableView.reloadData()
},
...

NOTE: the results passed to the resultsHandler are the results for that specific page, to access all results use paginator.results

resetHandler - Optional

The resetHandler allows you to do things like updating the UI or other activities that must be done after the data source has changed. It is optional.

paginator.resetHandler = {
    (paginator) in
    self.tableView.reloadData()
}

failureHandler - Optional

The failureHandler allows you to react to failures separately from the fetchHandler. It isn’t required, but is a decent way to split logic of fetching and reacting to failures.

paginator.resetHandler = {
    (paginator) in
    self.presentFailedAlert()
}