iosjsonswiftpaginationswift4

Swift4 - Pagination


I'm new in developing iOS app. Still don't know much. I successfully can display the articles well from first page only but fail to show other articles from other page when I scroll-up to view more. I really need help if somebody knows how to do pagination from my collection View. Below is the picture of my API that I use.

API

And below is my code where i get the data from api -- (Artikel.swift)

import Foundation
import Alamofire
import SwiftyJSON
import os.log

struct Artikel: ServiceCompletionHandler {
    static func getCategories (completionHandler: @escaping ArrayCompletionHandler) {
        let headers: HTTPHeaders = [
            "Accept": "application/json"
        ]
        let parameters: Parameters = [
            "secret_key": Config.api_key
        ]
        Alamofire.request("\(Config.server_address)/article-categories", method: .post, parameters: parameters, headers: headers)
            .responseJSON { (response) -> Void in
                switch response.result {
                case .success:
                    let json = JSON(response.result.value!)
                    if json["data"].exists() {
                        if let data = json["data"]["data"].array {
                            completionHandler(data, nil)
                        } else {
                            completionHandler([], nil)
                        }
                    } else {
                        completionHandler([], nil)
                    }
                case .failure(let error):
                    print(error)
                    completionHandler([], "Network error has occured.")
                }
        }
    }
    static func getArticles(_ category_id: Int, completionHandler: @escaping ArrayCompletionHandler) {
        let headers: HTTPHeaders = [
            "Accept": "application/json",
            "secret-key": Config.api_key]
        
        let value: Int = category_id
        let newcategory = String(describing: value)
        
//        var nextpages = NewsSectionViewController.url
    
        let new_api = Config.server_addforarticle + newcategory
        
        Alamofire.request(new_api, method: .get, headers: headers)
            .responseJSON { (response) -> Void in
                switch response.result {
                case .success:
                    let json = JSON(response.result.value!)
//                    dump(json, name: "testing")
                    if let articles = json["articles"]["data"].array {
                        completionHandler(articles, nil)
                       
                    }else{
                        completionHandler([], nil)
                    }
                case .failure(let error):
                    print(error)
                    completionHandler([], "Network error has occured.")
                }
        }
    }
    }

and here where i display my articles. But yeah, I cant do pagination it stuck to 4-5 articles only. I just dont have any idea how to do what I did in my android version.

NewsSectionViewController.swift

import UIKit
import SwiftyJSON
import AlamofireImage

protocol NewsSectionViewControllerDelegate {
    func updateArrowPosition()
}

class NewsSectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    
    // MARK: - Variables
    var itemIndex: Int = 0
    var category:JSON!
    var stories:[JSON] = []
    var delegate: NewsSectionViewControllerDelegate?
    
    // MARK: - Views
    @IBOutlet var titleLabel: UILabel!
    @IBOutlet var collectionView: UICollectionView!
    @IBOutlet var loadingIndicator: UIActivityIndicatorView!
    
    // MARK: - Constraint
    @IBOutlet var titleTopConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.titleLabel.text = category["name"].stringValue.uppercased()
        
        self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 114, 0)
        if #available(iOS 11.0, *) {
            if ((UIApplication.shared.keyWindow?.safeAreaInsets.top)! > CGFloat(0.0)) {
                self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 173, 0)
                self.collectionView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, 173, 0)
            }
        }
        
        self.loadingIndicator.startAnimating()
        Artikel.getArticles(category["id"].intValue) {
            (articles, error) in
            self.loadingIndicator.stopAnimating()
            if error == nil {
                self.stories = articles
                self.collectionView.reloadData()
            }
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // MARK: - Collection View
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.stories.count - 1
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        var cell: NewsItemCollectionViewCell?
        
        let item = stories[indexPath.row + 1]
        
        if indexPath.row == 0 {
            cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BigCell", for: indexPath) as? NewsItemCollectionViewCell
        } else {
            cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SmallCell", for: indexPath) as? NewsItemCollectionViewCell
        }
        
        cell!.titleLabel.text = item["title"].stringValue
        
        if let thumbUrlString = item["banner_url_large"].string {
            if let thumbUrl = URL(string: thumbUrlString) {
                cell?.coverImageView.af_setImage(withURL: thumbUrl)
            }
        }
        
        let wpDateFormatter = DateFormatter()
        wpDateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        
        let dayDateFormatter = DateFormatter()
        dayDateFormatter.dateStyle = .medium
        dayDateFormatter.doesRelativeDateFormatting = true
        
        let date = wpDateFormatter.date(from: item["date_publish_web"].stringValue)!
        cell!.timestampLabel.text = dayDateFormatter.string(from: date)
        
        return cell!
        
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let story = self.stories[indexPath.row + 1]
        
        let newsContents = self.storyboard?.instantiateViewController(withIdentifier: "NewsContent") as! NewsContentViewController
        newsContents.story = story
        newsContents.title = self.category["name"].string
        self.navigationController?.pushViewController(newsContents, animated: true)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        if indexPath.row == 0 {
            let width = UIScreen.main.bounds.width - 30
            return CGSize(width: width, height: 385 - 20)
        } else {
            let width = (UIScreen.main.bounds.width - 45) / 2
            return CGSize(width: width, height: 210)
        }
        
    }
    
    // MARK: - ScrollView
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView == self.collectionView {
            self.titleTopConstraint.constant = 30 - scrollView.contentOffset.y
        }
        if let delegate = self.delegate {
            delegate.updateArrowPosition()
        }
    }
    
    
    /*
     // MARK: - Navigation
     
     // In a storyboard-based application, you will often want to do a little preparation before navigation
     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     // Get the new view controller using segue.destinationViewController.
     // Pass the selected object to the new view controller.
     }
     */
    
}

Any help I really appreciate.


Solution

  • In NewsSectionViewController:

    1) add global variables:

    a) var nextPageUrl: String? ;

    b) let numberOfPagination = 5 (5 it's a number of items in one url page);

    c) add variable hasMoreItems

    var hasMoreItems: Bool {
        get {
            guard let count = nextPageUrl?.count, count > 0 else {
                return false
            }
            return true
        }
    }
    

    d) add variable numberOfItems

    var numberOfItems: Int {
        get {
            return secrets.count
        }
    }
    

    2) change this line self.stories = articles to self.stories += articles

    3) add function for pagination:

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    
        if (indexPath.item + 1 == numberOfItems) && ((indexPath.item + 1) % numberOfPagination == 0) {
    
            if self.presenter.hasMoreItems {
                self.loadData()
            }
        }
    }
    

    f) add func loadData()

    func loadData() {
        Artikel.getArticles(category["id"].intValue, nextPageUrl: nextPageUrl) {
            (articles, error) in
            self.loadingIndicator.stopAnimating()
            if error == nil {
                self.stories = articles
                self.collectionView.reloadData()
            }
        }
    }
    

    In Artikel:

    1) change getArticles function

    static func getArticles(_ category_id: Int, nextPageUrl: String?, completionHandler: @escaping ArrayCompletionHandler) {
    
        //...
    
        let new_api = nextPageUrl ?? Config.server_addforarticle + newcategory
    
        //...
    }
    

    2) in this function you can return nextPageUrl value in ArrayCompletionHandler