Infinite Scrolling using UICollectionView in Swift

27 / Jul / 2015 by Abhayam Rastogi 7 comments

Introduction

This tutorial is for iOS application developers to insert infinite scrolling in swift to achieve gallery effect using UICollectionView (Objective-C class). We will use UIScrollView delegate methods, UICollectionView & custom UICollectionViewCell for endless scrolling. You can add more items in collection view data source by adding images (.png) files in resource folder or can use web services to get data at run-time. Here we will play with UIScrollView delegate methods. We don’t need to add UIScrollView because UICollectionView is a subclass of UIScrollView.
Inheritance: NSObject->UIResponder->UIView->UIScrollView->UICollectionView.

 

Infinite Scrolling Using Collection View Demo Video

In this sample video you can see how image gallery can be implemented in iPhone using horizontal scrolling.

 

Getting Started

First create a new project iOS\Application\Single View Application template, click next. Enter “Infinite Scrolling” for the Product Name, Language “Swift”, Device “iPhone”, Core Data not checked and click next.
Then download the resource pack for this project e.i. images (.png) and store them in a “resource” folder or image assets.

Infinite (Endless) Scrolling Source Code

Add Infinite Scrolling View Controller class to your project. To do this, go to File\New\File and select iOS\Source\Swift File. Name the file InfiniteScrollingViewController.swift, and click Create.

Creating your Views

UICollectionView: Drag a UICollectionView into your ViewController from Object library. If you have basic knowledge of Autolayout then you can add constraints on collection view.
So far you have added views for you project. Open InfiniteScrollingViewController.swift.  You will see that the class has the following code in it already:
import UIKit
 
class ViewController: UIViewController {
 
  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
  }

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

Adding Properties to your InfiniteScrolling View Controller

To do this, add these following properties to “InfiniteScrollingViewController” (right before viewDidLoad):
@IBOutlet weak var infiniteScrollingCollectionView: UICollectionView!
private let reuseIdentifier = "InfiniteScrollingCell"
private var photosUrlArray = [String]()
let WINDOW_WIDTH = UIScreen.mainScreen().bounds.width
let WINDOW_HEIGHT = UIScreen.mainScreen().bounds.height
Add the following in the ViewDidLoad:
self.title = "Infinite Scrolling"
self.automaticallyAdjustsScrollViewInsets = false
photosUrlArray = ["A_Photographer.jpg","A_Song_of_Ice_and_Fire.jpg","Another_Rockaway_Sunset.jpg","Antelope_Butte.jpg"]
infiniteScrollingCollectionView?.delegate = self
infiniteScrollingCollectionView?.dataSource = self

UICollectionViewDataSource

Let’s start with the data source. In InfiniteScrollingViewController.swift add an extension with the following data-source methods:
#pragma mark - UICollectionView Datasource
extension InfiniteScrollingViewController : UICollectionViewDataSource{
 func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }
    
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return photosUrlArray.count
    }
    
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! InfiniteScrollingCell
        let photoName = photoForIndexPath(indexPath)
        
        cell.configureCell(photoName)
        
        return cell
    }
}

UICollectionView Flow Layout Delegate Methods

In InfiniteScrollingViewController.swift, add an extension with the following delegate methods. Here we multiply collection view item’s height by 1.00346 to achieve aspect ration for images.
#pragma mark - UICollectionView Flow Layout Delegate
extension InfiniteScrollingViewController : UICollectionViewDelegateFlowLayout {
func collectionView(collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
            
            let size:CGSize = CGSizeMake(WINDOW_WIDTH, (WINDOW_WIDTH)*1.203460)
            return size
            
    }
    
    func collectionView(collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        insetForSectionAtIndex section: Int) -> UIEdgeInsets {
            return UIEdgeInsetsMake(0, 0, 0, 0)
    }

}
Now before you run your project add custom collection view cell.

Adding custom collection view cell

Go to File\New\File and select iOS\Source\Swift File. Name the file InfiniteScrollingCell.swift, and click Create with xib.
Open the Xib file and drag the UIImageView from the object library.

Adding Properties to your InfiniteScrolling Collection View cell

To do this, add these following properties to “InfiniteScrollingCell.swift” (right before awakeFromNib):
@IBOutlet weak var imageView: UIImageView!

Configure collection view cell

Add the following at the end your collection view cell class:
func configureCell(photoName:String){
imageView?.image = UIImage(named: photoName)
}

Adding endless scrolling logic here

Concept of endless scrolling is “Rotate an array by k elements”. To understand code in detail. Open the “InfiniteScrollingViewController.swift” and at the end of class add the following methods:
func photoForIndexPath(indexPath: NSIndexPath) -> String {
        return photosUrlArray[indexPath.row]
    }
    
    
    func reversePhotoArray(photoArray:[String], startIndex:Int, endIndex:Int){
        if startIndex >= endIndex{
            return
        }
        swap(&photosUrlArray[startIndex], &photosUrlArray[endIndex])
        
        reversePhotoArray(photosUrlArray, startIndex: startIndex + 1, endIndex: endIndex - 1)
    }

UIScrollView Delegate Methods

#pragma mark - UIScrollView Delegate
extension InfiniteScrollingViewController:UIScrollViewDelegate{

   func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
        // Calculate where the collection view should be at the right-hand end item
      let fullyScrolledContentOffset:CGFloat = infiniteScrollingCollectionView.frame.size.width * CGFloat(photosUrlArray.count - 1)
      if (scrollView.contentOffset.x >= fullyScrolledContentOffset) {
            
      // user is scrolling to the right from the last item to the 'fake' item 1.
      // reposition offset to show the 'real' item 1 at the left-hand end of the collection view
       if photosUrlArray.count>2{  
         reversePhotoArray(photosUrlArray, startIndex: 0, endIndex: photosUrlArray.count - 1)
         reversePhotoArray(photosUrlArray, startIndex: 0, endIndex: 1)
         reversePhotoArray(photosUrlArray, startIndex: 2, endIndex: photosUrlArray.count - 1)
         var indexPath : NSIndexPath = NSIndexPath(forRow: 1, inSection: 0)               infiniteScrollingCollectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Left, animated: false)
            }
      }
      else if (scrollView.contentOffset.x == 0){
            
       if photosUrlArray.count>2{
        reversePhotoArray(photosUrlArray, startIndex: 0, endIndex: photosUrlArray.count - 1)
        reversePhotoArray(photosUrlArray, startIndex: 0, endIndex: photosUrlArray.count - 3)
        reversePhotoArray(photosUrlArray, startIndex: photosUrlArray.count - 2, endIndex: photosUrlArray.count - 1)
        var indexPath : NSIndexPath = NSIndexPath(forRow: photosUrlArray.count - 2, inSection: 0)
              infiniteScrollingCollectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Left, animated: false)
            }
        }
    }
}

You can download the completed project from here.

FOUND THIS USEFUL? SHARE IT

comments (7)

  1. Sanjeev

    Its not a generic solution. Even if i scrolled for the first time the condition if (scrollView.contentOffset.x >= fullyScrolledContentOffset) is satisfied and automatically scrolls.

    Reply
  2. Himanshu Gupta

    //
    // ViewController.swift
    // DoIt
    //
    // Created by Sierra 4 on 03/03/17.
    // Copyright © 2017 codeBrew. All rights reserved.
    //

    import UIKit

    let reuseIdentifier = “Cell”

    class ViewController: UIViewController {

    @IBOutlet weak var lblOutlet: UILabel!

    @IBOutlet weak var viewLbl: UIView!
    @IBOutlet weak var lblOutlet1: UILabel!
    var images: [String] = Bundle.main.paths(forResourcesOfType: “png”, inDirectory: “Images”)

    //var arr : [String] = [“Himanshu”, “Shefali”, “Abhinandan”, “Shalvi”, “Manpreet”, “Pratyush”,”Sapna”,”Prince”,”Chitvan”,”Palak”,”Aditi”,”Megha”]
    // var arrInfo : [String] = [“IOS Intern at Codebrew Labs”, “IOS Intern at company 2”, “IOS Intern at company 11”, “IOS Intern at company 21”, “IOS Intern at company 29”, “IOS Intern at company 19″,”IOS Intern at company 7″,”IOS Intern at company 9″,”IOS Intern at company 26″,”IOS Intern at company 6″,”IOS Intern at company 13″,”IOS Intern at company 10”]

    @IBOutlet weak var collectionView: UICollectionView!

    override func viewDidLoad() {
    super.viewDidLoad()
    viewLbl.isHidden = true
    collectionView!.register(UINib(nibName: “CollectionViewCell”, bundle: nil), forCellWithReuseIdentifier: reuseIdentifier)
    collectionView.backgroundView = nil
    collectionView.backgroundColor = UIColor.clear

    }

    func photoForIndexPath(_ indexPath: IndexPath) -> String {
    return images[indexPath.row]
    }

    func reversePhotoArray(_ photoArray:[String], startIndex:Int, endIndex:Int){
    if startIndex >= endIndex{
    return
    }
    swap(&images[startIndex], &images[endIndex])

    reversePhotoArray(images, startIndex: startIndex + 1, endIndex: endIndex – 1)
    }

    }
    extension ViewController:UICollectionViewDataSource,UICollectionViewDelegate
    {

    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 = collectionView.dequeueReusableCell(withReuseIdentifier: “cell”, for: indexPath) as! CollectionViewCell

    let imageName = photoForIndexPath(indexPath)

    cell.configureCell(imageName)

    cell.imageName = images[indexPath.row]
    return cell
    }
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // lblOutlet.text = arr[indexPath.item]
    // lblOutlet1.text = arrInfo[indexPath.item]
    viewLbl.isHidden = false

    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    // Calculate where the collection view should be at the right-hand end item
    let fullyScrolledContentOffset:CGFloat = collectionView.frame.size.width * CGFloat(images.count – 1)
    if (scrollView.contentOffset.x >= fullyScrolledContentOffset) {

    // user is scrolling to the right from the last item to the ‘fake’ item 1.
    // reposition offset to show the ‘real’ item 1 at the left-hand end of the collection view
    if images.count>2{
    reversePhotoArray(images, startIndex: 0, endIndex: images.count – 1)
    reversePhotoArray(images, startIndex: 0, endIndex: 1)
    reversePhotoArray(images, startIndex: 2, endIndex: images.count – 1)
    let indexPath : IndexPath = IndexPath(row: 1, section: 0)
    collectionView.scrollToItem(at: indexPath, at: .left, animated: false)
    }
    }
    else if (scrollView.contentOffset.x == 0){

    if images.count>2{
    reversePhotoArray(images, startIndex: 0, endIndex: images.count – 1)
    reversePhotoArray(images, startIndex: 0, endIndex: images.count – 3)
    reversePhotoArray(images, startIndex: images.count – 2, endIndex: images.count – 1)
    let indexPath : IndexPath = IndexPath(row: images.count – 2, section: 0)
    collectionView.scrollToItem(at: indexPath, at: .left, animated: false)
    }
    }
    }

    }

    Reply
  3. DragonCherry

    Hi. I’ve implemented it recently for my personal purpose. Anyone can check my project below. Infinitely loop through views, recycle views, automatically fits current view on center, and so on.

    github.com/DragonCherry/HFSwipeView

    Reply
  4. Marko

    First I want to thank you for your nice and effective code. I have also found another project, implementing similar strategy for infinite scroll but I noticed in both projects small fluctuating (shivering) of images just a bit before scroll will stop. Do you have any advice hot to prevent this? Thank you, Marko

    Reply
    1. Abhayam Rastogi

      Hi Marko,

      I have done some changes in the project’s scrollViewDidEndDeceleration & viewDidLod methods to reduce images fluctuation. Please try this drive.google.com/file/d/0BzGWvRnGZYdDQUlkMVdjTWZwbTQ/view?usp=sharing to download improved project.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *