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...
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
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
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
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
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
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.
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
With this data in place, we again lean on the on the built in collection view functionality and subclass UICollectionViewDataSource
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!
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
Top comments (1)
Hello, I developed a lightweight general cache library with swift 5. If you happen to use swift to develop IOS projects, if you need to use the cache library, maybe you can try swiftlycache, which I developed, and maybe you will like it
github.com/hlc0000/SwiftlyCache