Infinite Scrolling using UICollectionView in Swift
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.
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.
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) } } } } |
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.
please help me out….scroll view not working
//
// 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)
}
}
}
}
Thanks, man, for sharing 🙂 It helps a lot!!
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
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
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.