Building a calendar in Swift
Tags: swift, ios
Published: 2020-05-18 18:15:35

In this post, I wanted to walk through an approach I used when building a calendar view in Swift for an iOS app I’m working on. The requirements were as follows:

* Should start from the current day, and then scroll backwards through time
* Each month is it’s own section, with corresponding header
* Days should be a selectable square cell, and we should have 3 days on each row
* The grid should take up as much screen real estate as possible
* It should be memory efficient

And with any luck, it will look something like this...

![a calendar view layout](https://dev-to-uploads.s3.amazonaws.com/i/t73ffkwjmegqnz6h9ezt.png)

With this in mind, I decided to use a customised `UICollectionView`, as most of the work around grid layouts and memory optimisation are done for me.

## Bring the calendar to life

The first step in this process is to initialise a collection view. That’s relatively simple as follows

{% gist https://gist.github.com/wdchris/ba89c28cc459e41051c559409de10e2e.js %}

Most of this code is layout plumbing such as setting the frame size to it’s container, along with constraints to anchor it's size. This will fulfil my requirement around maximising screen real estate.

## Go with the flow (layout)

Next up, I wanted to further optimise the layout such that the day cells are as big as possible. To achieve this, we can subclass `UICollectionViewFlowLayout`. The important methods to implement are as follows

{% gist https://gist.github.com/wdchris/4e1ff210252e17505edfebdb85299840.js %}

Here we are calculating the maximum size of cell, based on the current frame and padding used. This should yield 3 day cells on each row, each maximising the width.

This subclass now needs be registered with my collection view as follows

{% gist https://gist.github.com/wdchris/7dd0aa62e965b14f916abb7f1e4d7493.js %}

## Make it fancy

The final piece of layout required are definitions for what the day cells and month section headers will look like. To achieve this, I created two simple nibs. The day cells need to subclass `UICollectionViewCell` as follows

{% gist https://gist.github.com/wdchris/04fae466c85f445818c52bccd0c27688.js %}

Nothing out of the ordinary here, just a label being set. I did however implement static convenience functions so that details such as reuse identifiers are encapsulated. As for the month header, we need to subclass from `UICollectionReusableView`

{% gist https://gist.github.com/wdchris/0ffc270571e8eb6636c06d154a2cb685.js %}

Note how we register this as a kind of `UICollectionView.elementKindSectionHeader`. We now need to tell our collection view to use these templates when creating new header and collection cells.

{% gist https://gist.github.com/wdchris/693479789b3fae38b380639391b114a3.js %}

## Let there be data

Finally, we need to tell the collection what it’s actually supposed to be rendering. I achieved this by firstly implementing a manager class which will calculate the necessary data, and put it into an efficient struct format.

The reason for this is that we can calculate this once, upfront, and then simply lookup the relevant data at render time (as opposed to re-calculating for each cell). Here is the supporting code to decide how many months we need to show, along with how many days each month contains

{% gist https://gist.github.com/wdchris/6e7d35e32e473954f109ee00509d032f.js %}

With this data in place, we again lean on the on the built in collection view functionality and subclass `UICollectionViewDataSource`

{% gist https://gist.github.com/wdchris/65df03050531a88f6df56dd01ab1302c.js %}

As you can see, returning the relevant section and cell data is just a lookup from our array. We can now hook this data source up to the collection view and away we go!

{% gist https://gist.github.com/wdchris/5e9de3ff1adf04d28fba4c7a899e2269.js %}

Looking back through the approach I have taken, I'm pleased to say it meets all of the requirements I set out at the top. This makes me happy.

You can see the full Swift calendar source code for this solution here: https://github.com/wdchris/calendar-grid-swift