As a follow-up to my previous tutorial “Creating an infinite grid on iOS”: https://itnext.io/creating-an-infinite-grid-on-ios-2bd6db28c581; based on user comments I decided to see if it was possible to do the same using a UICollectionView.

After some flamboyant failures (attempting to use 50.5GB of memory on an iPhone can’t possibly go well), I’ve managed to get the sample code to run using only 76MB of memory.

Re-defining the requirements:

Must use only UIKit native classes

Scrolling should have the expected feel of a scrollview

User must have the impression they can scroll forever

Must be memory efficient

Content must be generated in tiles so as to construct a grid

Initial coordinate should be specifiable

EXTRA: Must use UICollectionView

EXTRA: Each grid tile must be a UICollectionViewCell

EXTRA: UICollectionViewCell allocation/deallocation must be managed by UICollectionView and be re-usable

Understanding the updated constraints:

As per the previous tutorial, we have some elements preventing us from going too wild:

UICollectionView expects a finite set of sections and items per sections before having to “reloadData”

Specifying a very large number of sections with large number of items per section requires too much memory and is not suitable to an infinite grid implementation

UICollectionView by default will scroll only horizontally or vertically, scrolling on both axis will require a custom UICollectionViewLayout

UICollectionView is a subclass of UIScrollView, and as such when the user reaches the edges the view would stop or bounce; we require to scroll infinitely

Getting started, setting up the UICollectionView

Xcode project — Single View application

Starting with a “Single View” iOS application, we will start by creating a custom UICollectionView class named InfiniteGrid:

InfiniteGrid.swift — base file

Then updating the base view controller to initialize our empty grid:

Base app — nothing to see yet!

It’s not much to look at yet. You can confirm the UICollectionView is properly hosted by changing the background color in InfiniteGrid.swift line 8.

Registering and displaying a cell

Let’s now define the basis of our cell. For this we will match the design of the previous tutorial which simply displays a UILabel with the coordinates. As a first step, let’s define GridCoordinates data type:

GridCoordinates.swift — base & final implementation

The GridCoordinates data type will be used throughout our tutorial to represent the x,y coordinates of our grid tiles. Next a UICollectionViewCell subclass which displays the GridCoordinates on a label:

InfiniteGridCell.swift — base & final implementation

There are a few nuggets of code in there which may need explaining.

InfiniteGridCell.register(with:) can be used to register the cell with the collection view so we can easily dequeue it later

can be used to register the cell with the collection view so we can easily dequeue it later InfiniteGridCell.dequeue(…) allows to easily dequeue the cell from the collection view and set the coordinates to display on the cell.

allows to easily dequeue the cell from the collection view and set the coordinates to display on the cell. The coordinatesLabel() function is used to instantiate a UILabel in code or access one previously created.

function is used to instantiate a UILabel in code or access one previously created. The coordinates variable holds the coordinates associated with that grid cell and will automatically update the label with a textual representation when updated.

Let’s now create a temporary data source for our collection view so we can display the cell and confirm that part works.

InfiniteGridDataSource.swift — displaying one cell

And update the InfiniteGrid.swift to define the data source and register the cell:

InfiniteGrid.swift — updated with cell registration & data source

Running our app, we can now confirm the cell shows up with a label indicating the coordinates specified in our data source:

App screenshot — displaying one cell on top left

As per screenshot, we can see the coordinates 3,-2 which were specified in our InfiniteGridDataSource.swift line 12.

Introducing a custom UICollectionViewLayout

For our solution to work, we need to provide a custom UICollectionViewLayout which will allow the user to scroll both vertically and horizontally. As a first step, let’s define an arbitrary large grid work area, and position one cell in its centre:

InfiniteGridLayout.swift — base layout file

We will now update the InfiniteGrid to use our custom layout:

InfiniteGrid.swift — updated with InfiniteGridLayout

Line 8 was updated by replacing UICollectionViewFlowLayout() with the custom InfiniteGridLayout().

We also added a scrollToCenter() which can be called at any time to refocus to the center of the grid work area. Updating the ViewController to ensure the grid is centered on viewDidAppear:

ViewController.swift — added scrollToCenter()

App screenshot — showing base InfiniteGridLayout usage

Adding our grid

So far we’ve used 3,-2 as the coordinate to show on our demo grid cell, as we are starting to build our complete grid, we will be using 0,0 as the center as it will be easier to confirm proper implementation.

As a first step, let’s define a centerCoordinates for our InfiniteGrid:

InfiniteGrid.swift — centerCoordinates

Next we need to update the InfiniteGridDataSource to keep a local cache of matching IndexPath to GridCoordinates, as well as dequeuing the cells and assigning the proper coordinates:

InfiniteGridDataSource.swift — IndexPath to GridCoordinates tracking

At line 4 we assign a value to our pathsCacheSize, while the exact value is not important it should be larger than the number of cells UICollectionView will have cached at any point in time. If this value is too small, some cells will not appear properly when scrolling.

The assignPath(to:) function records a GridCoordinates for an IndexPath so it can be retrieved in collectionView(:cellForItemAt:).

Line 27 does some index magic, increasing the pathsCacheIndex to the next IndexPath and making sure it wraps up to 0 after pathsCacheSize increments. The % performs a modulo division — sorry couldn’t resist throwing that small optimization in there!

Next, we need to implement the InfiniteGridLayout computations:

InfiniteGridLayout.swift — grid layout computations

The layoutAttributesForElements(in:) function is called by UICollectionView, we compute the coordinates at the origin and opposite end of the rectangle then return a list of UICollectionViewLayoutAttributes for each grid cell.

Notice how we use dataSource.assignPath(to:) function to request the next usable IndexPath from the InfiniteGridDataSource.

Running the app, we now see a grid!

App screenshot, working grid

Let’s do a spot check for our memory usage:

Performance metrics & memory requirements

Looks good! Our only issue left to fix is to change our relatively large grid into an infinite grid.

Making our grid infinite

So far, we created a finite grid which, if the user is persistent enough, after dragging for a while we are going to hit the edges and bounce. Since UICollectionView is based on UIScrollView, we can use some UIScrollViewDelegate functions to detect when dragging has ended and re-set the contentOffset and centerCoordinates:

InfiniteGridDelegate.swift — base & final implementation

Let’s also update our InfiniteGrid to automatically instantiate and set the custom delegate:

InfiniteGrid.swift — final implementation

The end result, is our collection view being reset to centre every time it is no longer active. Visually, the only changes are the scrollview indicators being reset to middle.

To more easily test and confirm the implementation is working properly, open up InfiniteGridLayout.swift and change the gridSize to around 2000 x 2000. As the collection view is scrolled, the scroll indicators will reach their limit and the view will eventually bounce. As soon as no dragging operation is happening, the scroll indicators should reset to the center and with the collection view being draggable again. — Don’t forget to reset the gridSize once you are done!

To complete the infinite grid implementation, the scrollview indicators should be hidden.

Source code

Source code for this project is hosted on GitHub: https://github.com/freshcode/Infinite-CollectionViewGrid-Swift

About the author

Dave Poirier is a senior software developer, currently working on creating some really interesting iOS applications at ID Fusion Software Inc.

Need help with your mobile app software development? Visit our website at http://idfusion.com