There is a massive number of iOS applications available on the App Store these days, and every single one of them looks different.
Despite that, there’s one thing that most applications have in common: lists. From the early days, iOS developers had access to the famous UITableView and used it a lot. The UITableView, however has some major limitations. It has to be vertical, and there’s not a lot you can customise about its layout.
Luckily, in iOS 6, UICollectionView came to the rescue and addressed both problems mentioned above. In this blogpost, I want to show how we can benefit from customising the UICollectionViewLayout. Apart from a theoretical introduction that I’ve seen so many times on so many blogs, I will also share the problems I would like to solve and entire solutions for resolving them. In order not to make this too overwhelming, I’ll start with simple things and move up to the more advanced aspects step by step. Everything you will see here you can also download from GitHub, split into nice and easy-to-understand modules – see the link at the end. I know that there's some force unwrapping and code repetitions. I’ve included them on purpose in order to make the code easier to read and understand. This introduction is already too long, so, without further ado, let’s dive into the beautiful world of customising the UICollectionView layouts!
Theoretical introductionThe most important thing you have to know before you begin is that there are two classes you can subclass to achieve a custom look for your list: UICollectionViewLayout and UICollectionViewFlowLayout. The first paragraph of the official guide for custom layouts describes it perfectly, and there’s not a lot I could add to this:
Before you start building custom layouts, consider whether doing so is really necessary. The UICollectionViewFlowLayout class provides a significant amount of behaviour that has already been optimized for efficiency and that can be adapted in several ways to achieve many different types of standard layouts. The only times to consider implementing a custom layout are in the following situations:
- The layout you want looks nothing like a grid or a line-based breaking layout (a layout in which items are placed into a row until it’s full, then continue on to the next line until all items are placed) or necessitates scrolling in more than one direction.
- You want to change all of the cell positions frequently enough that it would be more work to modify the existing flow layout than to create a custom layout.
You will see exactly this approach in my examples. Whenever it’s possible, just subclass the UICollectionViewFlowLayout and don't re-invent the wheel ;)
There are so many methods you could override when subclassing those classes that there’s no point in explaining them all here. As I’ve stated in the title, this blogpost is a “Practical Introduction”, so I’ll just jump straight into the examples. However, if you want to really understand the topic, you should read the official guide for UICollectionView (especially the two sections: Using the Flow Layout and Creating Custom Layout).
Example 1: Snapping a cell to the centre
Let’s start with something really simple! Imagine that we have a vertical list of flower images. Nothing fancy, an image in a cell covers almost the whole screen, and you can scroll down to see other cells. It would be very nice to snap this image to the centre of the screen when scrolling. It’s important to do it without any glitches or jumps being visible to the user. You can achieve something similar by changing the pagingEnabled parameter for UIScrollView, but here, you should be able to scroll multiple items in a single pan gesture. You can see what I mean in the image above. You can also download the project from GitHub and tweak it yourself. This is a perfect example for starting your journey of customising layouts. Still following the rule of not re-inventing the wheel, let’s start with customising UICollectionViewFlowLayout.
The first thing we have to do is to subclass UICollectionViewFlowLayout and set up some basic parameters that you could also easily do without using a subclass of the layout. However, it’s a good approach to keep as much code as possible away from the UIViewController – it already has so many responsibilities in our UIKit world these days. This is the thing you would see in all of the presented layouts, so I’ll explain it only once. Because the prepare method can be called several times (for example when invalidating the layout), I’ve added an additional variable to make sure I don't call the setup method more than once. Line number 17 is especially interesting in this case, because it makes the snapping effect look nicer. The parameter decelerationRate is available in UIScrollView, and since UICollectionView is a child of UIScrollView, we can make use of it.
We can already make use of our layout subclass, but we have to tell UICollectionView about it first. We can do it in the storyboard or in the code – in the example below, I’ve used the code approach.
Our application will work now, but there’s still no snapping effect, so let’s add it. In the layout subclass, override another method called targetContentOffset(forProposedContentOffset: withScrollingVelocity:). There are two similar methods, so make sure you pick the correct one! Now, the system tells you what content offset it wants to set, and you can tweak it in any way you want. In our implementation, we’ll try to find the closest cell to the proposed offset and return it, taking into account the bounds of the cell and the whole collection view. You can see that we use an element of the type UICollectionViewLayoutAttributes to access the data of items. Now we are using it only as an information provider, but in the following examples, we will tweak those parameters and also create our own subclasses of it! Make sure to read the documentation of this class – it’s really short and easy to understand. Now you need to build and run the project. You should see that cells snap to the centre of the screen when scrolling. In a few lines of code, we achieved a snapping feature in our UICollectionView without any logic in the cell or view controller! I hope you already see why subclassing collection view layouts can benefit your app without making it harder to read!
Example 2: Horizontal carousel
Let’s move on to something nicer. What if I told you that you could make your list look like a carousel by adding only about 40 lines of code to the previous example? No, it’s not a joke. Just have a look at the gif above. I’ve seen libraries in the past doing similar things, and they contained hundreds of lines of code. Curious how to do this? I hope so!
Like before, just subclass UICollectionViewFlowLayout and set up your collection view. You would need to add some insets to the beginning and the end of the content to be able to display the whole first and last element. Now override the shouldInvalidateLayout(forBoundsChange) method and just return true. This tells the UICollectionView that every time the user scrolls it, it should ask its layout class what the items should look like. One of the methods that UICollectionView asks for is called layoutAttributesForElements(in rect:). Override this one and magic will happen. In the first line, let’s put the array of already computed attributes using exactly the same method we are overriding, however, not using this class, but its superclass. If you don’t use super at the beginning, your app will freeze and crash after a while because of recurrency.
Okay, you have the attributes, so let’s tweak these. It’s an array of attributes, because in a given rect, there might be a few items visible at the time, and every item will have its own attributes. Because it’s an array, let’s iterate over every element, check how close it is to the centre of the screen and, depending on this, tweak the scale and alpha, and transform. The hardest part is the transformation – when writing code like this you will really appreciate the few years of math studies you have done. The last overridden method is responsible for snapping cells to the centre and it looks almost identically to the one in the previous example. That said, since we have horizontal scrolling, we have to tweak the X parameter of the point instead of the Y. I haven’t included this method in the snippet below, but it’s there in the final project. Now you can build and run the project and see the beautiful carousel effect you’ve just created.
Example 3: Parallax effect
Okay, now let’s see what we can build by subclassing UICollectionViewLayout. The effect you can see above is called a parallax, and has become very popular over the past few years. It’s still fairly easy to implement, but because of subclassing UICollectionViewLayout instead of UICollectionViewFlowLayout, we will have more code to write. In this case, I won’t explain everything in detail like before – I'll only show you the general idea instead. As always, all the code is available on GitHub.
We can start exactly the same way as before by subclassing the UICollectionViewLayout and creating your setup method. Also like before, we have to return true from the shouldInvalidateLayout(forBoundsChange) method. In this case, we won't calculate everything on the fly, but we’ll do it once in the prepare method, and we'll put the calculated layout attributes in the cache. We could have also done it this way in the previous example, but I wanted to show you two approaches. The rule of thumb is to use the cache when there are a lot of calculations that could negatively impact performance needed to prepare the layout. We’ll also create our own subclass of layout parameters, because the default one doesn’t have a parameter called “parallax”. When creating your own subclass of layout parameters, you have to inform your custom layout about this by overriding layoutAttributesClass and returning this class type from the method.
The most important part is happening exactly like before in the layoutAttributesForElements(in rect) method. Override it, pull the proper attribute from the cache, and apply the proper parallax with some calculations. These calculations are really similar to the previous ones, but instead of setting the alpha or the scale, we are setting the parallax parameter. However, as I said before, the default layout attribute doesn’t have a parallax parameter, so it doesn’t know how to apply this parameter to the cell. No fear, just override the apply method in your UICollectionViewCell subclass and use your custom parallax parameter. It’s something new, so you can see the code in the snippet below. Build, run, and marvel at your awesome parallax effect.
Example 4: Swapping layouts
This example will be very short but really mind-blowing. You have already prepared some nice custom layouts, so there’s no need to do another one. Now we want to swap these layouts with animation. Sounds really hard, because animations are always hard, especially when layouts inside also animate during the scroll. Luckily, UICollectionView can do everything for you. The only thing necessary to make an animated swap of layouts is to call one single method! You can see it in the snippet below. Of course, it’s not perfect in this case, but you can fix this. In your custom layout, you can override the methods responsible for preparing the view for the swap animations.
The options for customising the collection view layouts are endless. There are tonnes of methods you can override. It’s impossible to describe them all in one blogpost, but if you want to become a master in customising collection view layouts, just read the documentation. Don’t forget to write the code too – you won’t understand it by only reading the docs. This was the reason I’ve written this practical tutorial, and I hope it was helpful. If you have any questions, just write a comment or mention me on Twitter: @kwiecien_co.
Link to the repository: GitHub