https://github.com/johnvuko/jttableviewcontroller
A ViewController for manage pagination and loaders for iOS.
https://github.com/johnvuko/jttableviewcontroller
Last synced: about 1 year ago
JSON representation
A ViewController for manage pagination and loaders for iOS.
- Host: GitHub
- URL: https://github.com/johnvuko/jttableviewcontroller
- Owner: johnvuko
- License: mit
- Created: 2015-04-15T20:57:15.000Z (about 11 years ago)
- Default Branch: master
- Last Pushed: 2019-01-17T17:05:13.000Z (over 7 years ago)
- Last Synced: 2025-04-06T21:01:51.052Z (about 1 year ago)
- Language: Swift
- Homepage:
- Size: 1.08 MB
- Stars: 61
- Watchers: 4
- Forks: 9
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# JTTableViewController
[](https://travis-ci.org/jonathantribouharet/JTTableViewController)



A ViewController with a tableView which manage pagination and loaders for iOS.
## Installation
With [CocoaPods](http://cocoapods.org/), add this line to your Podfile.
pod 'JTTableViewController', '~> 2.0'
## What's in it?
- avoid parallel requests problem (you start two requests and the first one finish after the second), last request is the only one we want to use
- easily manage pagination
- display a view for the first loading (when your `tableView` is empty)
- display a view when there is no results to your first request
- display a loader view (an `UITableViewCell`) for indicate the next page is loading
- display a view for errors
## Screenshots

## Usage
### Minimum usage
You can either use `JTFullTableViewController` which is almost like `UITableViewController`.
Or you can inherit from `JTTableViewController`,`UITableViewDelegate`, `UITableViewDataSource`, create an `UITableView`, assign it to `self.tableView`, add it to the `self.view` and set the `delegate` and `dataSource` yourself.
If your controller is fullscreen use `JTFullTableViewController` else use `JTTableViewController`.
```swift
import JTTableViewController
class ViewController: JTTableViewController, UITableViewDelegate, UITableViewDataSource {
// Used in this example to manage your pagingation
private var currentPage = 1
// Must be implemented
override func jt_tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let anInstanceOfYourModel = self.results[indexPath.row]
/*
whatever you wanna do with `anInstanceOfYourModel` and your `cell`
*/
return cell
}
// Must be implemented
override func fetchResults() {
super.fetchResults()
currentPage = 1
// `lastRequestId` is used to avoid problem with parallel requests
let lastRequestId = self.lastRequestId
YourService.retrieveData(page: currentPage) { (error, results) -> () in
if let error = error {
self.didFailedToFetchResults(error: error, lastRequestId: lastRequestId)
return
}
self.didFetchResults(results: results, lastRequestId: lastRequestId) {
// this block is executed if `lastRequestId` matched with `self.lastRequestId`
self.currentPage += 1
}
}
}
// Must be implemented
override func fetchNextResults() {
super.fetchNextResults()
// `lastRequestId` is used to avoid problem with parallel requests
let lastRequestId = self.lastRequestId
YourService.retrieveData(page: currentPage) { (error, results) -> () in
if let error = error {
self.didFailedToFetchResults(error: error, lastRequestId: lastRequestId)
}
else {
self.didFetchNextResults(results: results, lastRequestId: lastRequestId) {
// this block is executed if `lastRequestId` matched with `self.lastRequestId`
self.currentPage += 1
}
}
}
}
}
```
### Advanced usage
```swift
import JTTableViewController
class ViewController: JTTableViewController, UITableViewDelegate, UITableViewDataSource {
// Used in this example to manage your pagingation
private var currentPage = 1
private let refreshControl = UIRefreshControl()
override func viewDidLoad () {
super.viewDidLoad()
// `nextPageLoaderCell` is an `UITableViewCell`
self.nextPageLoaderCell = MyNextPageLoadCell()
// `fecthResults` is call 5 cells before `nextPageLoaderCell` become visible
self.nextPageLoaderOffset = 5
// `noResultsView` is display when `didFetchResults` is called with an `results` empty
let noResultsView = NoResultsView()
self.noResultsView = noResultsView
self.view.addSubview(noResultsView)
// something better than frame with Constraints but not relevant here
noResultsView.frame = self.view.bounds
// `noResultsLoadingView` is display when `fetchResults` is called and `results` is empty
let noResultsLoadingView = NoResultsLoadingView()
self.noResultsLoadingView = noResultsLoadingView
self.view.addSubview(noResultsLoadingView)
// something better than frame with Constraints but not relevant here
noResultsLoadingView.frame = self.view.bounds
// `errorView` is display when `didFailedToFetchResults` is called
let errorView = ErrorView()
self.errorView = errorView
self.view.addSubview(errorView)
// something better than frame with Constraints but not relevant here
errorView.frame = self.view.bounds
refreshControl.addTarget(self, action: #selector(fetchResults), for: .valueChanged)
self.tableView?.addSubview(refreshControl)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchResults()
}
// Must be implemented
override func jt_tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let anInstanceOfYourModel = self.results[indexPath.row]
/*
whatever you wanna do with `anInstanceOfYourModel` and your `cell`
*/
return cell
}
// Must be implemented
override func fetchResults() {
self.resetResults()
super.fetchResults()
currentPage = 1
// `lastRequestId` is used to avoid problem with parallel requests
let lastRequestId = self.lastRequestId
YourService.retrieveData(page: currentPage) { (error, results) -> () in
if let error = error {
self.didFailedToFetchResults(error: error, lastRequestId: lastRequestId)
return
}
self.didFetchResults(results: results, lastRequestId: lastRequestId) {
// this block is executed if `lastRequestId` matched with `self.lastRequestId`
self.currentPage += 1
}
}
}
// Must be implemented
override func fetchNextResults() {
super.fetchNextResults()
// `lastRequestId` is used to avoid problem with parallel requests
let lastRequestId = self.lastRequestId
YourService.retrieveData(page: currentPage) { (error, results) -> () in
if let error = error {
self.didFailedToFetchResults(error: error, lastRequestId: lastRequestId)
}
else {
self.didFetchNextResults(results: results, lastRequestId: lastRequestId) {
// this block is executed if `lastRequestId` matched with `self.lastRequestId`
self.currentPage += 1
}
}
}
}
override func didEndFetching () {
super.didEndFetching()
refreshControl.endRefreshing()
}
}
```
You have to implement `fetchResults` and `fetchNextResults` methods. They are used to load data (from your web service for example). These methods must call `super`.
Optionaly, you can override `jt_tableView(tableView:heightForRowAt:)` for defining the height of cells.
`fetchResults` is used to retrieve new data (erase all previous data) whereas `fetchNextResults` is used for get more data (the pagination).
`didFetchResults` must be call when `fetchResults` have successfuly retrieve data.
`didFetchNextResults` must be call when `fetchNextResults` have successfuly retrieve data.
`didFailedToFetchResults` must be call if `didFetchResults` or `didFetchNextResults` have failed to retrieve data.
`didEndFetching` is called after `didFetchResults`, `didFetchNextResults` or `didFailedToFetchResults`
The data are stored in `results`. Just use `self.results` to access to them.
If you want to remove some elements in `results` you can use `self.unsafeResults`, only in specific case (ex: removing one cell).
There are some properties you can customize:
- `nextPageLoaderCell` is the loader use for the pagination, it's a `UITableViewCell`
- `noResultsView` is the view display when the results get from your service are empty
- `noResultsLoadingView` is the view display when there is no results and you start fetching new data, used for the first load
- `errorView` is the view display `didFailedToFetchResults` is called
- `nextPageLoaderOffset` is the number of cells require before the last cell for calling `fetchNextResults`, by default it's 3
You can also override some methods:
- `didEndFetching`
- `showNoResultsLoadingView`
- `hideNoResultsLoadingView`
- `showNoResultsView`
- `hideNoResultsView`
- `showErrorView`
- `hideErrorView`
### Sections
`JTTableViewController` support section management. You just have to override 2 method:
- `numberOfSections(tableView:)`
- `jt_tableView(tableView:numberOfRowsInSection:)`
```swift
import JTTableViewController
class ViewController: JTTableViewController<[Int: [YourModel]]>, UITableViewDelegate, UITableViewDataSource {
@objc(numberOfSectionsInTableView:)
override func numberOfSections(in tableView: UITableView) -> Int {
return self.results.count
}
override func jt_tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results[section].count
}
}
```
## Subclassing notes
If you want to subclass `JTTableViewController` or `JTFullTableViewController`, the methods from `UITableViewDelegate` and `UITableViewDataSource` must have an `@objc` annotation.
```swift
class XXTableViewController: JTTableViewController {
/*
... Here you add whatever you want to add
*/
}
class MyViewController: XXTableViewController {
// if you don't add `@objc(tableView:didSelectRowAtIndexPath:)` this method is not called
@objc(tableView:didSelectRowAtIndexPath:)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/*
...
/*
}
}
```
## Requirements
- iOS 8.0 or higher
- Swift 3.0
## Author
- [Jonathan VUKOVICH TRIBOUHARET](https://github.com/jonathantribouharet) ([@johnvuko](https://twitter.com/johnvuko))
## License
JTTableViewController is released under the MIT license. See the LICENSE file for more info.