A ViewController with a tableView which manage pagination and loaders for iOS.
With CocoaPods, add this line to your Podfile.
pod 'JTTableViewController', '~> 2.0'
- 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
tableViewis 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
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.
import JTTableViewController
class ViewController: JTTableViewController<YourModel>, 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
}
}
}
}
}import JTTableViewController
class ViewController: JTTableViewController<YourModel>, 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:
nextPageLoaderCellis the loader use for the pagination, it's aUITableViewCellnoResultsViewis the view display when the results get from your service are emptynoResultsLoadingViewis the view display when there is no results and you start fetching new data, used for the first loaderrorViewis the view displaydidFailedToFetchResultsis callednextPageLoaderOffsetis the number of cells require before the last cell for callingfetchNextResults, by default it's 3
You can also override some methods:
didEndFetchingshowNoResultsLoadingViewhideNoResultsLoadingViewshowNoResultsViewhideNoResultsViewshowErrorViewhideErrorView
JTTableViewController support section management. You just have to override 2 method:
numberOfSections(tableView:)jt_tableView(tableView:numberOfRowsInSection:)
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
}
}If you want to subclass JTTableViewController or JTFullTableViewController, the methods from UITableViewDelegate and UITableViewDataSource must have an @objc annotation.
class XXTableViewController<T>: JTTableViewController<T> {
/*
... Here you add whatever you want to add
*/
}
class MyViewController: XXTableViewController<YourModel> {
// if you don't add `@objc(tableView:didSelectRowAtIndexPath:)` this method is not called
@objc(tableView:didSelectRowAtIndexPath:)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/*
...
/*
}
}- iOS 8.0 or higher
- Swift 3.0
JTTableViewController is released under the MIT license. See the LICENSE file for more info.
