Building a Seamless Infinite Wrap-Around Carousel in Swift Using UICollectionView (No External Libraries)

Taku Muguti
DVT Software Engineering
5 min readJul 24, 2023

In today’s digital world, user engagement is a top priority for designers and developers alike. One effective way to captivate your audience is by implementing an infinite scroll carousel. This feature allows users to easily find the item they are looking for inside the carousel as they will reach it regardless of what direction they scroll in.

Preview of infinite carousel.

By the end of this tutorial, you will know how to build a captivating infinite scroll carousel that wraps around seamlessly using Swift UICollectionView, as shown above. The best part? We won’t rely on any external libraries, giving you complete control over the implementation.

Advantages of using this implementation:

  • It gives the illusion that the content is wrapping around itself, meaning it will never reach the end making the carousel infinite.
  • Very few duplicates. (the buffer size is the maximum number of items that can be displayed on the screen if you only show 1 item at a time, then the buffer can be 1 item).
  • This solution provides a smooth user interaction as it capitalises on UICollectionView’s default scroll mechanism.

For this tutorial, we won’t delve into the details of setting up the project and adding a basic UICollectionView. I have attached a GitHub link to see the full implementation. However, there are a few important configurations we need to cover to proceed successfully.

  1. Hide the Horizontal scroll indicator.
  2. Make sure the bounce on scroll property is enabled (Very Important otherwise, it will not work.)
Configuring the collection view.

How UICollectionView handles scrolling

Before diving head first into the code, it’s essential to understand how the swift Collection View handles scrolling. As the user performs a scroll gesture, the scroll view adjusts which portion of the underlying content is visible. This is done by adjusting the content offset. Below I will attach a diagram to try to illustrate this.

Diagram visualising how scrolling works.
illustration of scroll action

In the diagram above, The collection View which I drew outside the screen, is used to illustrate that scrolling does not move the collection view it simply offsets the content within the view as the user scrolls.

Making the content wrap around

For this tutorial, I will be using a collection view with 4 elements. We also need a buffer. The buffer is the maximum number of elements visible on the screen. In our case, that will be 3. With this buffer in place, the key to creating an infinite scroll is to keep our content offset in a specific range. If the content offset is bigger than our predetermined upper bound, we simply subtract the range. If it is smaller than 0, we add the range (this is why we need it to ensure that bounce on scroll is enabled; otherwise, it would never become smaller than 0).

Below are diagrams to explain the concept better.

Diagram showing how the buffer is added.
visual explanation of the above.
Diagram showing scrolling.
scrolling infinitely to the right
Diagram showing scrolling
scrolling infinitely to the left

Implementing the code

Now that we understand how it works, we can look at how to implement it in code. Do you recall how we mentioned that we need to add a “buffer”? We can achieve this by inserting duplicate items in our actual array. However, adding the buffer without modifying the original array would be a much cleaner solution. To do that, we need to add a few extra variables.

let buffer = 3 //max items visible at the same time.
var totalElements = 0

One to store the buffer and the other to store the total number of items in the collection view. We now need to update the numberOfItemsInSection to reflect our new total.

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
totalElements = buffer + models.count
return totalElements
}

We then need to update the cellForItemAt function to use the modulus operator. This is done to ensure that we always use an item within the bounds of our array.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as? CollectionViewCell else{
return UICollectionViewCell()
}
let currentCell = indexPath.row % models.count
cell.configure(with: models[currentCell])

return cell
}

We now have everything in place to implement the wrap-around logic. To do so, we first need to determine our upper bound by determining the size (width) of one item, which can be calculated by dividing the collection views content width by the total number of items. The upper bound is then calculated by multiplying the item size by the number of items in our original array. We can now implement conditional statements to ensure our content offset remains in range.

extension ViewController: UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let itemSize = collectionView.contentSize.width/CGFloat(totalElements)

if scrollView.contentOffset.x > itemSize*CGFloat(models.count){
collectionView.contentOffset.x -= itemSize*CGFloat(models.count)
}
if scrollView.contentOffset.x < 0 {
collectionView.contentOffset.x += itemSize*CGFloat(models.count)
}
}
}

And with that, our carousel is complete. Feel free to explore the source code on GitHub, and if you have any questions or suggestions to improve the code, please reach out to me on LinkedIn.

Bonus (Page indicator)

One might want to add a page indicator below the carousel to help orient the user as they scroll through the items. This is very easy to implement. We just need to make sure we update the page number as the user scrolls. To do that, we calculate the current index by dividing the current offset by the width of one item, then modulo the number of items in our original array. This calculation then tells us which item is at the leftmost of the screen.

extension ViewController: UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let itemSize = collectionView.contentSize.width/CGFloat(totalElements)

if scrollView.contentOffset.x > itemSize*CGFloat(models.count){
collectionView.contentOffset.x -= itemSize*CGFloat(models.count)
}
if scrollView.contentOffset.x < 0 {
collectionView.contentOffset.x += itemSize*CGFloat(models.count)
}
//Determine the current Index and update pageIndicator
let currentIndex = Int(round(Double(collectionView.contentOffset.x)/Double(itemSize))) % models.count
pageIndicator.currentPage = currentIndex
}
}

--

--