im having trouble setting up pagination in swift with the MovieDB API
normally you would have a limit and an offet then that would relay to your model array .count -1
when working with CollectionViews
Im working with a diffable datasource and cannot see the solution
has anyone manage to implement this or something similar?
current API service looks like this
class APIService {
static let shared = APIService()
//always pass in your first API so the one which holds title, release date ect
func fetchMovies(completionHandler: #escaping ([Movie]?, Error?) -> ()) {
guard let url = URL(string: APINOWPLAYING) else {
print("not a valid url")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data {//when Decoding use the 2nd API model with the array
if let decodedResponse = try? JSONDecoder().decode(Movies.self, from: data) {
DispatchQueue.main.async {
completionHandler(decodedResponse.results, nil)
print("TOTAL RESULTS \(decodedResponse.page)")
}
return
}
}
print("Fatch Failed \(error?.localizedDescription ?? "error unknown")")
}.resume()
}
view controller
private func setupDiffableDataSource() {
collectionView.dataSource = diffDataSource
//MARK:- SetupHeader under Compositional Sections Extension
setupHeader()
APIService.shared.fetchMovies { (movies, err) in
APIService.shared.fetchTopMovies { (movieGroup, err) in
var snapshot = self.diffDataSource.snapshot()
snapshot.appendSections([.topSection])
snapshot.appendItems(movies ?? [], toSection: .topSection)
snapshot.appendSections([.bottomSection])
let objects = movieGroup?.results ?? []
snapshot.appendItems(objects, toSection: .bottomSection)
self.diffDataSource.apply(snapshot)
}
}
}
does anyone know how to work with API for pagination?
this is what the MOVIEDB api call looks like
let APINOWPLAYING =
"https://api.themoviedb.org/3/movie/now_playing?api_key=(APIKEY)&language=en-US&page=1&total_pages=56"
hoping someone can point me in the right direction
thanks
You can use func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) from UICollectionViewDelegate
You need to update your service so it can handle the page parameter
var isCanLoadMore = false
var currentPage = 1
private func fetchData(page: Int) {
// your API request
// remember to change isCanLoadMore = true after apply(snapshot)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if isCanLoadMore {
if diffableDataSource.snapshot().numberOfSections - 1 == indexPath.section {
let currentSection = diffableDataSource.snapshot().sectionIdentifiers[indexPath.section]
if diffableDataSource.snapshot().numberOfItems(inSection: currentSection) - 1 == indexPath.row {
isCanLoadMore = false
currentPage += 1
print("NEXT PAGE")
fetchData(page: currentPage)
}
}
}
}
Related
we need to pass count in JSON parameter like this
var currentPageNumberVM: Int = 0
"count": currentPageNumber
and in service call I'm getting JSON data like below here JSON data is coming and data is showing in collectionview but pagination is not working
func serviceCall(){
self.currentPageNumberVM+=10
let param = ["jsonrpc": "2.0",
"params": ["type" : type, "count": currentPageNumberVM]] as [String : Any]
APIReqeustManager.sharedInstance.serviceCall(param: param, vc: self, url: getUrl(of: .productByFeature), header: header) {(responseData) in
if responseData.error != nil{
self.view.makeToast(NSLocalizedString("Something went wrong!", comment: ""))
}else{
self.viewmoreDB = ViewMoreBase(dictionary: responseData.dict as NSDictionary? ?? NSDictionary())
self.productsData = self.viewmoreDB?.result?.products
self.collectionView.reloadData()
}
}
}
I'm adding values to collectionview like below
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return productsData?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HorizontalSliderCollectionCell", for: indexPath) as! HorizontalSliderCollectionCell
let indexData = productsData?[indexPath.item]
cell.lblDescrip.text = indexData?.product_by_language?.des
cell.lblTitle.text = indexData?.product_by_language?.title
return cell
}
for pagenation i am trying like below: but nothing works
var isLoading = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if (offsetY > contentHeight - scrollView.frame.height * 4) && !isLoading {
loadMoreData()
}
}
func loadMoreData() {
if !self.isLoading {
self.isLoading = true
DispatchQueue.global().async {
// Fake background loading task for 2 seconds
sleep(2)
// Download more data here
DispatchQueue.main.async {
self.collectionView.reloadData()
self.isLoading = false
}
}
}
}
How to add pagination to collectionview? .. I mean after loading 10 cells..below need to show activityindicator.. and load another 10 cells
how to do this, please do help me
Could you try this? but first, note that you need to pass the counter as a parameter in your call service function:
This code will be added in your cellForItemAt :
let lastPost = postsArray.count - 1
if lastPost == indexPath.row {
if limit < 100{
limit += 10
callAPI()
}
while callAPI function is:
func callAPI () {
PostService.getPosts(limit: limit) { (postsArray, error) in
if error == nil {
guard let postsArray = postsArray else {return}
self.postsArray = postsArray
DispatchQueue.main.async {
self.postsCollectionView.reloadData()
}
}
}
}
}
Of course, you will change the naming dependent on your project.
Hope it helps
I'm getting data from Reddit API and displaying reddit posts in the tableview. I want to load 5 (for example) posts from api at a time and display them, and then load next 5 posts when the user scroll down to the last fifth cell.
Maybe I need to change my logic of getting posts from api. Please advice. Thank you!
I'm using MVVM. Populate tableview cells with posts data in TableViewController:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell") as! PostTableViewCell
vm.getPost(at: indexPath.row) { post in
guard let post = post else { print("No post"); return }
print(post)
DispatchQueue.main.async {
cell.usernameLabel.text = post.username
cell.domainLabel.text = post.domain
cell.titleLabel.text = post.title
cell.detailsLabel.text = post.text
cell.postImage.sd_setImage(with: URL(string: "\(post.imageURL)"))
cell.timePassedLabel.text = post.createdHoursAgo
cell.ratingButton.setTitle("\(post.rating)", for: .normal)
cell.commentsButton.setTitle("\(post.comments)", for: .normal)
}
// pagination
}
return cell
}
Get one post:
class PostViewModel {
var limit = 30
let sub = "ios"
var portion = 5
var post: RedditPost?
func getPost(at postIndex: Int, completion: (#escaping (_ data: RedditPost?) -> Void)) {
UseCase().createPosts(sub: sub, limit: limit) { posts in
// there are not so many posts on reddit as you asked for (as limit)
if self.limit > posts.count {
print("Error: Number of demanded posts are bigger than available on Reddit")
completion(nil)
} else {
let post = posts[postIndex]
self.post = post
completion(post)
}
}
}
}
Get all posts from API:
class UseCase {
func createPosts(sub: String, limit: Int, completion: (#escaping (_ data: [RedditPost]) -> Void)) {
Repository().fillPostsArray(sub: sub, limit: limit) { (redditPosts: [RedditPost]) in
completion(redditPosts)
}
}
}
the first API call should be made from the viewDidLoad method and reload the tableView when the response comes.
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
loadPostsFromRedit()
}
private func loadPostsFromRedit() {
vm.getPost(at: indexPath.row) { [weak self] posts in
guard let self = self, let post = posts else {
return
}
print(posts)
self.post = posts
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Pagination:
You can use the willDisplayCell method to check if the user has reached the end of the tableView and call the API to load the next page
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if post.count == indexPath.row - 1 {
vm.loadMorePosts()
}
}
Note: you also need to pass the page index to redit API to return the correct page response. Refer this tutorial for more details https://www.raywenderlich.com/5786-uitableview-infinite-scrolling-tutorial
I would like to save this kind of arrays with Core Data:
let crypto1 = Cryptos(name: "Bitcoin", code: "bitcoin", symbol: "BTC", placeholder: "BTC Amount", amount: "0.0")
let crypto2 = Cryptos(name: "Bitcoin Cash", code: "bitcoinCash", symbol: "BCH", placeholder: "BCH Amount", amount: "0.0")
Is that even possible?
I know I can create an array to save like that...
let name = "Bitcoin"
let code = "bitcoin"
let symbol = "BTC"
let placeholder = "BTC Amount"
let amount = "0.0"
let cryptos = CryptoArray(context: PersistenceService.context)
cryptos.name = name
cryptos.code = code
cryptos.symbol = symbol
cryptos.placeholder = placeholder
cryptos.amount = amount
crypto.append(cryptos)
PersistenceService.saveContext()
...but this seems pretty inconvenient when a theoretical infinite number of arrays will be created by the user.
What would be the best way for me to save data, load it, edit it and delete it?
This is a question for a tutorial rather than a straight forward answer. I suggest you give some time to read about CoreData. Having said that, your question sounds generic, "Saving array to CoreData in Swift", so I guess it doesn't hurt to explain a simple implementation step by step:
Step 1: Create your model file (.xcdatamodeld)
In Xcode, file - new - file - iOS and choose Data Model
Step 2: Add entities
Select the file in Xcode, find and click on Add Entity, name your entity (CryptosMO to follow along), click on Add Attribute and add the fields you like to store. (name, code, symbol... all of type String in this case). I'll ignore everything else but name for ease.
Step 3 Generate Object representation of those entities (NSManagedObject)
In Xcode, Editor - Create NSManagedObject subclass and follow the steps.
Step 4 Lets create a clone of this subclass
NSManagedObject is not thread-safe so lets create a struct that can be passed around safely:
struct Cryptos {
var reference: NSManagedObjectID! // ID on the other-hand is thread safe.
var name: String // and the rest of your properties
}
Step 5: CoreDataStore
Lets create a store that gives us access to NSManagedObjectContexts:
class Store {
private init() {}
private static let shared: Store = Store()
lazy var container: NSPersistentContainer = {
// The name of your .xcdatamodeld file.
guard let url = Bundle().url(forResource: "ModelFile", withExtension: "momd") else {
fatalError("Create the .xcdatamodeld file with the correct name !!!")
// If you're setting up this container in a different bundle than the app,
// Use Bundle(for: Store.self) assuming `CoreDataStore` is in that bundle.
}
let container = NSPersistentContainer(name: "ModelFile")
container.loadPersistentStores { _, _ in }
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
// MARK: APIs
/// For main queue use only, simple rule is don't access it from any queue other than main!!!
static var viewContext: NSManagedObjectContext { return shared.container.viewContext }
/// Context for use in background.
static var newContext: NSManagedObjectContext { return shared.container.newBackgroundContext() }
}
Store sets up a persistent container using your .xcdatamodeld file.
Step 6: Data source to fetch these entities
Core Data comes with NSFetchedResultsController to fetch entities from a context that allows extensive configuration, here is a simple implementation of a data source support using this controller.
class CryptosDataSource {
let controller: NSFetchedResultsController<NSFetchRequestResult>
let request: NSFetchRequest<NSFetchRequestResult> = CryptosMO.fetchRequest()
let defaultSort: NSSortDescriptor = NSSortDescriptor(key: #keyPath(CryptosMO.name), ascending: false)
init(context: NSManagedObjectContext, sortDescriptors: [NSSortDescriptor] = []) {
var sort: [NSSortDescriptor] = sortDescriptors
if sort.isEmpty { sort = [defaultSort] }
request.sortDescriptors = sort
controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
// MARK: DataSource APIs
func fetch(completion: ((Result) -> ())?) {
do {
try controller.performFetch()
completion?(.success)
} catch let error {
completion?(.fail(error))
}
}
var count: Int { return controller.fetchedObjects?.count ?? 0 }
func anyCryptos(at indexPath: IndexPath) -> Cryptos {
let c: CryptosMO = controller.object(at: indexPath) as! CryptosMO
return Cryptos(reference: c.objectID, name: c.name)
}
}
All we need from an instance of this class is, number of objects, count and item at a given indexPath. Note that the data source returns the struct Cryptos and not an instance of NSManagedObject.
Step 7: APIs for add, edit and delete
Lets add this apis as an extension to NSManagedObjectContext:
But before that, these actions may succeed or fail so lets create an enum to reflect that:
enum Result {
case success, fail(Error)
}
The APIs:
extension NSManagedObjectContext {
// MARK: Load data
var dataSource: CryptosDataSource { return CryptosDataSource(context: self) }
// MARK: Data manupulation
func add(cryptos: Cryptos, completion: ((Result) -> ())?) {
perform {
let entity: CryptosMO = CryptosMO(context: self)
entity.name = cryptos.name
self.save(completion: completion)
}
}
func edit(cryptos: Cryptos, completion: ((Result) -> ())?) {
guard cryptos.reference != nil else {
print("No reference")
return
}
perform {
let entity: CryptosMO? = self.object(with: cryptos.reference) as? CryptosMO
entity?.name = cryptos.name
self.save(completion: completion)
}
}
func delete(cryptos: Cryptos, completion: ((Result) -> ())?) {
guard cryptos.reference != nil else {
print("No reference")
return
}
perform {
let entity: CryptosMO? = self.object(with: cryptos.reference) as? CryptosMO
self.delete(entity!)
self.save(completion: completion)
}
}
func save(completion: ((Result) -> ())?) {
do {
try self.save()
completion?(.success)
} catch let error {
self.rollback()
completion?(.fail(error))
}
}
}
Step 8: Last step, use case
To fetch the stored data in main queue, use Store.viewContext.dataSource.
To add, edit or delete an item, decide if you'd like to do on main queue using viewContext, or from any arbitrary queue (even main queue) using newContext or a temporary background context provided by Store container using Store.container.performInBackground... which will expose a context.
e.g. adding a cryptos:
let cryptos: Cryptos = Cryptos(reference: nil, name: "SomeName")
Store.viewContext.add(cryptos: cryptos) { result in
switch result {
case .fail(let error): print("Error: ", error)
case .success: print("Saved successfully")
}
}
Simple UITableViewController that uses the cryptos data source:
class ViewController: UITableViewController {
let dataSource: CryptosDataSource = Store.viewContext.dataSource
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "YourCellId", for: indexPath)
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cryptos: Cryptos = dataSource.anyCryptos(at: indexPath)
// TODO: Configure your cell with cryptos values.
}
}
You cannot save arrays directly with CoreData, but you can create a function to store each object of an array. With CoreStore the whole process is quite simple:
let dataStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "ModelName")
do {
try dataStack.addStorageAndWait()
} catch let error {
print("Cannot set up database storage: \(error)")
}
return dataStack
}()
func addCrypto(name: String, code: String, symbol: String, placeholder: String, amount: Double) {
dataStack.perform(asynchronous: { transaction in
let crypto = transaction.create(Into<Crypto>())
crypto.name = name
crypto.code = code
crypto.symbol = symbol
crypto.placeholder = placeholder
crypto.amount = amount
}, completion: { _ in })
}
You can show the objects within a UITableViewController. CoreStore is able to automatically update the table whenever database objects are added, removed or updated:
class CryptoTableViewController: UITableViewController {
let monitor = dataStack.monitorList(From<Crypto>(), OrderBy(.ascending("name")), Tweak({ fetchRequest in
fetchRequest.fetchBatchSize = 20
}))
override func viewDidLoad() {
super.viewDidLoad()
// Register self as observer to monitor
self.monitor.addObserver(self)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.monitor.numberOfObjects()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CryptoTableViewCell", for: indexPath) as! CryptoTableViewCell
let crypto = self.monitor[(indexPath as NSIndexPath).row]
cell.update(crypto)
return cell
}
}
// MARK: - ListObjectObserver
extension CryptoTableViewController : ListObjectObserver {
// MARK: ListObserver
func listMonitorWillChange(_ monitor: ListMonitor<Crypto>) {
self.tableView.beginUpdates()
}
func listMonitorDidChange(_ monitor: ListMonitor<Crypto>) {
self.tableView.endUpdates()
}
func listMonitorWillRefetch(_ monitor: ListMonitor<Crypto>) {
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Crypto>) {
self.tableView.reloadData()
}
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<Crypto>, didInsertObject object: Switch, toIndexPath indexPath: IndexPath) {
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didDeleteObject object: Switch, fromIndexPath indexPath: IndexPath) {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didUpdateObject object: Crypto, atIndexPath indexPath: IndexPath) {
if let cell = self.tableView.cellForRow(at: indexPath) as? CryptoTableViewCell {
cell.update(object)
}
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didMoveObject object: Switch, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
self.tableView.insertRows(at: [toIndexPath], with: .automatic)
}
}
Assuming that you have a CryptoTableViewCell with the function update registered to the CryptoTableViewController.
I am working on a services app in which user creates a post whose details are saved in a dynamoDb table. I have fetched all the data in the table and now i want to display the data in collection view controller such that each cell represents single post. Now i am not sure how to segregate every single post from that data and provide it to collection view. My table fields are:
Table_Screenshot
My code is:
import UIKit
import AWSDynamoDB
class ProvidingViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var PCV: UICollectionView!
let db = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
var counter:Int = 0
var imagex = ["UserIcon.png", "chat2.png","UserIcon.png", "delete.png","UserIcon.png", "delete.png","UserIcon.png", "delete.png","UserIcon.png", "delete.png","UserIcon.png", "delete.png"]
var images:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
scanner()
}
///
func scanner(){
scanExpression.limit = 2000
db.scan(PostDetails.self, expression: scanExpression).continueWith(block: { (task:AWSTask!) -> AnyObject! in
if task.result != nil {
let paginatedOutput = task.result!
//use the results
for item in paginatedOutput.items as! [PostDetails] {
self.counter = paginatedOutput.items.count
self.images.append(item.userId!)
}
if ((task.error) != nil) {
print("Error: Could not fetch PostDetails table data")
}
return nil
}
return nil
})
}
///
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = PCV.dequeueReusableCell(withReuseIdentifier: "c", for: indexPath) as! CellsCollectionViewCell
cell.ProvImage.image = UIImage(named: imagex[indexPath.row])
cell.ProvLabel.text = images[indexPath.row]
return cell
}
}
I have images array in which i am fetching data. When i print out the array it has data but when i assign it to collection view controller, screen is displayed empty i.e no cells. Please help. Thanks
The real issue you are facing is that when View loads the images array is empty and CollectionView loads empty and when you are loading images in the array in the scanner method you are not calling reloadData for CollectionView that is why you are not able to see anything in the CollectionView after data is being loaded into your array. I am updating your scanner method , try this and it will work.
func scanner(){
scanExpression.limit = 2000
db.scan(PostDetails.self, expression: scanExpression).continueWith(block: { (task:AWSTask!) -> AnyObject! in
if task.result != nil {
let paginatedOutput = task.result!
//use the results
for item in paginatedOutput.items as! [PostDetails] {
self.counter = paginatedOutput.items.count
self.images.append(item.userId!)
}
//This line is important because it tells collectionview that i have new data so please refresh.
PCV.reloadData()
if ((task.error) != nil) {
print("Error: Could not fetch PostDetails table data")
}
return nil
}
return nil
})
}
I am trying to implement a pagination in a demo app. I am using a UICollectionView to display a lot of images from an API using SDWebImage. And the API supports pagination like this:
My problem is how to show this nextPage's images to my collectionview?
{
"meta":{
"code":200
},
"data":{ },
"pagination":{
"total":86,
"totalPages":3,
"page":1,
"nextPage":2,
"nextPageUrl":"http://.............?page=2"
}
}
And my aim is that to show this nextPageUrl's pic to the collectionview.
and here is my code :
class StoreViewController: UIViewController,UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet var MyStoreCollectionView: UICollectionView!
var alldata: [PopulerMagazalarData]?
var indexPath: IndexPath?
var storeData : [PopulerMagazalarData] = []
let pagenumber = 1
override func viewDidLoad() {
super.viewDidLoad()
if let indexPath = self.indexPath, let storeData = self.alldata?[indexPath.row] {
let storeusername = storeData.username
GetDataFromUrl(from: "https://............./\(storeusername!)?page=\(pagenumber)")
}
}
And my data get fun from url ...
func GetDataFromUrl(from:String){
Alamofire.request(from, method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
self.storeData = [PopulerMagazalarData]()
//...Creating Data Obj.
let data = PopulerMagazalarData()
let username = json["data"]["store"]["user"]["username"].string
let userpic = json["data"]["store"]["user"]["profilePicture"].string
let productsCount = json["data"]["store"]["productsCount"].int
let description = json["data"]["store"]["description"].string
let followedby = json["data"]["store"]["user"]["counts"]["followedBy"].int
let count:Int? = json["data"]["products"].array?.count
if let ct = count {
for index in 0...ct-1{
let images = json["data"]["products"][index]["images"]["standart"]["url"].string
data.img1 = images
self.storeData.append(data)
}
}
//*****************
data.username = username
data.profilPic = userpic
data.producsCount = productsCount
data.desc = description
data.followedby = followedby
//******************
self.storeData.append(data)
// for refresh collecitonView
self.refresh_now()
case .failure(let error):
print(error)
}
}
}
//...CollectionView ReloadData func...
func refresh_now(){
DispatchQueue.main.async (
execute:
{
self.MyStoreCollectionView.reloadData()
}
)
}
and this is my collectionview funds :
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return 1
}
Please check my this answer first, add a footer view to your collectionview when footerview appears make a network call, append new array to previous array and reload your collectionview
Try this easy solution with help of CCBottomRefreshControl You need to just treat it like simple UIRefreshController.
let bottomRefreshController = UIRefreshControl()
bottomRefreshController.triggerVerticalOffset = 50
bottomRefreshController.addTarget(self, action: #selector(ViewController.refreshBottom), forControlEvents: .ValueChanged)
collectionView.bottomRefreshControl = bottomRefreshController
func refreshBottom() {
//api call for loading more data
loadMoreData()
}