Swift tableView Pagination

I have success working tableview with json parsing codes.But may have 1000 more item so need pagination when scrolling bottom side. I dont know how can i do this my codes under below. For objective-c have a lot of example but for swift i didnt find working example. Im waiting your helps. I think will be help too many people. Thank you !

import UIKit

class ViewController: UIViewController, UITableViewDataSource,UITableViewDelegate {

    let kSuccessTitle = "Congratulations"
    let kErrorTitle = "Connection error"
    let kNoticeTitle = "Notice"
    let kWarningTitle = "Warning"
    let kInfoTitle = "Info"
    let kSubtitle = "You've just displayed this awesome Pop Up View"


    @IBOutlet weak var myTableView: UITableView!
    @IBOutlet weak var myActivityIndicator: UIActivityIndicatorView!

    var privateList = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        loadItems()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return privateList.count
    }




    internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {

       let cell:myCell = tableView.dequeueReusableCellWithIdentifier("myCell") as! myCell

        cell.titleLabel.text = privateList[indexPath.row]


        return cell
    }


    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        if (editingStyle == UITableViewCellEditingStyle.Delete){

         print(indexPath.row)


            let alert = SCLAlertView()
            alert.addButton("Hayır"){ }
            alert.addButton("Evet") {

                self.myTableView.beginUpdates()

                 self.privateList.removeAtIndex(indexPath.row)
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Left)
                print("Silindi")

                self.myTableView.endUpdates()

                  self.loadItems()

            }
            alert.showSuccess(kSuccessTitle, subTitle: kSubtitle)

        }


    }





    func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // the cells you would like the actions to appear needs to be editable
        return true
    }



    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {


        if(segue.identifier == "Detail") {

            let destinationView = segue.destinationViewController as! DetailViewController

            if let indexPath = myTableView.indexPathForCell(sender as! UITableViewCell) {

                destinationView.privateLista = privateList[indexPath.row]

            }
        }
    }



    internal func tableView(tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat
    {
        return 0.0
    }


    func loadItems()
    {
     loadItemsNow("privateList")

    }

    func loadItemsNow(listType:String){
        myActivityIndicator.startAnimating()
        let listUrlString =  "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString
        let myUrl = NSURL(string: listUrlString);
        let request = NSMutableURLRequest(URL:myUrl!);
        request.HTTPMethod = "GET";

        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
            data, response, error in

            if error != nil {
                print(error!.localizedDescription)
                dispatch_async(dispatch_get_main_queue(),{
                    self.myActivityIndicator.stopAnimating()
                })

                return
            }


            do {

                let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray

                if let parseJSON = json {


                        self.privateList = parseJSON as! [String]

                }

            } catch {
                print(error)

            }

            dispatch_async(dispatch_get_main_queue(),{
                self.myActivityIndicator.stopAnimating()
                self.myTableView.reloadData()
            })


        }

        task.resume()
    }


}

  • iOS5 TabBar Fonts and Color
  • Alamofire : How to handle errors globally
  • “Couldn't send a valid signature” error when using CloudKit without iCloud account
  • Dropbox iOS SDK: WebKit Discarded an Uncaught Exception
  • FBSDKAppInviteDialog in Facebook iOS SDK version 4.0.1 invite successfully sent, but no notifications received
  • Disable cell reuse for small fixed-size UITableView
  • 6 Solutions Collect From Internet About “Swift tableView Pagination”

    For that you need to have server side change also.

    1. Server will accept fromIndex and batchSize in the API url as query param.

      let listUrlString =  "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString + "&batchSize=" + batchSize + "&fromIndex=" + fromIndex
      
    2. In the server response, there will be an extra key totalItems. This will be used to identify all items are received or not. An array or items fromIndex to batchSize number of items.

    In the app side

    1. First loadItem() will be called with fromIndex = 0 and batchSize = 20 (for example in viewDidLoad() or viewWillAppear). removeAll items from privateList array before calling loadItem() for the first time

    2. Server returns an array of first 20 items and totalItems total number of items in the server.

    3. Append the 20 items in privateList array and reload tableView

    4. In tableView:cellForRowAtIndexPath method check if the cell is the last cell. And check if totalItems (form server) is greater than privateList.count. That means there are more items in the server to load

      if indexPath.row == privateList.count - 1 { // last cell
          if totalItems > privateList.count { // more items to fetch
              loadItem() // increment `fromIndex` by 20 before server call
          }
      }
      

    Question: where is refresh ? will be scrolling ?

    Refresh after appending new items in the array when server response received. (step 3)

    Scrolling will trigger tableView:cellForRowAtIndexPath for every cell when user scrolls. Code is checking if it is the last cell and fetch remaining items. (step 4)

    Sample project added:
    https://github.com/rishi420/TableViewPaging

    The good and efficient way to do it is by using scrollviewDelegate in tableview
    Just add UIScrollViewDelegate in your viewController
    In view controller

    //For Pagination
    var isDataLoading:Bool=false
    var pageNo:Int=0
    var limit:Int=20
    var offset:Int=0 //pageNo*limit
    var didEndReached:Bool=false
    viewDidLoad(_){
    tableview.delegate=self //To enable scrollviewdelegate
    }
    

    Override two methods from this delegate

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    
            print("scrollViewWillBeginDragging")
            isDataLoading = false
        }
    
    
    
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
            print("scrollViewDidEndDecelerating")
        }
        //Pagination
        func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    
                print("scrollViewDidEndDragging")
                if ((tableView.contentOffset.y + tableView.frame.size.height) >= tableView.contentSize.height)
                {
                    if !isDataLoading{
                        isDataLoading = true
                        self.pageNo=self.pageNo+1
                        self.limit=self.limit+10
                        self.offset=self.limit * self.pageNo
                        loadCallLogData(offset: self.offset, limit: self.limit)
    
                    }
                }
    
    
        }
    

    Add another section to your tableview, let this section have only 1 row which will be a cell containing an activity indicator, to denote loading.

    internal func numberOfSectionsInTableView(tableView: UITableView) -> Int
    {
        return 2;
    }
    
    internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
        {
            if section == 0 {
                return privateList.count
            } else if section == 1 {    // this is going to be the last section with just 1 cell which will show the loading indicator
                return 1
            }
        }
    
    internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {
       if section == 0 {
           let cell:myCell = tableView.dequeueReusableCellWithIdentifier("myCell") as! myCell
    
            cell.titleLabel.text = privateList[indexPath.row]
    
    
            return cell
        } else if section == 1 { 
            //create the cell to show loading indicator
            ...
    
            //here we call loadItems so that there is an indication that something is loading and once loaded we relaod the tableview
            self.loadItems()
        }
    }
    

    Another way of doing this is: You may set a threshold for getting elements while sending request each time:

    Lets say you you are fetching 20 elements first time. You will be saving last fetched record id or number for getting list of next 20 elements.

    let lastFetchedIndex = 20;
    

    I am assuming that you have already added these records in your myArray. MyArray is the dataSource of tableView. Now myArray is containing 40 objects. I am going to make a list of indexPaths of rows that needs to be inserted in tableView now.

    var indexPathsArray = [NSIndexPath]()
    
    
    for index in lastFetchedIndex..<myArray.count{
        let indexPath = NSIndexPath(forRow: index, inSection: 0)
        indexPathsArray.append(indexPath)
    
    }
    

    Here I am updating my tableView. Make sure your dataSource i mean your myArray has already been updated. So that it may insert rows properly.

    self.tableView.beginUpdates()
    tableView!.insertRowsAtIndexPaths(indexPathsArray, withRowAnimation: .Fade)
    self.tableView.endUpdates()
    

    I needed something similar on a project and my solution was:

    1 – create a variable numberOfObjectsInSubArray (initial value 30 or whatever you want)

    2 – create a subarray to add a number of objects from your privateList array every time i tap “show more”

        let subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))
    

    And use it on

    internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return subArray.count
    }
    

    3- Whenever you need to show more objects, do:

    func addMoreObjectsOnTableView () {
    
        numberOfObjectsInSubArray += 30
    
        if (numberOfObjectsInSubArray < privateList.count) {
    
            subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))  
    
        } else {
    
            subArray = privateList?.subarrayWithRange(NSMakeRange(0, privateList.count))  
        }
    
        tableView.reloadData()
    }
    

    I hope it helps

    here is a sample code for collection view :

    var page = 0
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
        print("page Num:\(page)")
    }
    
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath){
         if arrImagesData.count-1 == indexPath.row && arrImagesData.count%10 == 0{
            getMoreImages(page)
         }
    }
    
    func getMoreImages(page:Int){ 
       //hit api
       if api_success == true {
           if self.page == 0 {
              self.arrImagesData.removeAll()
           }
       self.arrImagesData.appendContentsOf(api_data)
       self.collectionImages.reloadData()
       self.page = self.page + 1
       }
    }