< Back to articles

How I Tackled the Undocummented Jungle of UICollectionViewLayout

Every iOS dev knows this kind of scenario “Hey man, we have this custom collectionView layout and it is not sizing correctly during the first appearance, can you look at it?” And here comes the first issue, all iOS devs use UICollectionView a lot 💪 , but I am not sure how many ever created their own custom layout 🧐, especially today when we have UICollectionViewCompositionalLayout since iOS 13. Well how hard can it be to find a simple solution to first appearance issue. 😎

gif of a man saying how hard it can be

The problem is even bigger if the layout you are supposed to fix is not yours but written by someone entirely different with the same knowledge of custom layout as you have – which is basically zero 😱 Based on the number (and order ☝️) of desperate steps you are sentenced to hours, days or weeks of more or less absurd attempts to make your layout work. Now I will guide you through my tour of those steps. 🙂

I am the best nothing can surprise me 😎

In the beginning, when you are given this task, you are so excited to resolve it. So you start debugging 🕵️‍♂️ As debugging layout is quite tricky using breakpoints you start adding debug prints throughout your codebase 🖨 As your cells have incorrect size you start with printing layout attributes in  cell’s preferredLayoutAttributesFitting(...) method to see what layout attributes your cell thinks that it needs. So your log for single cell looks like this:

[PREFERRED_LAYOUT_ATTRIBUTES] ==== START ====
[PREFERRED_LAYOUT_ATTRIBUTES] IndexPath: 0 - 0
[PREFERRED_LAYOUT_ATTRIBUTES] Original: (0.0, 0.0, 428.0, 200.0)
[PREFERRED_LAYOUT_ATTRIBUTES] Preferred: (0.0, 0.0, 428.0, 200.0)
[PREFERRED_LAYOUT_ATTRIBUTES] ==== END ====

Damn, why does the cell think it should be 200pts even though its content is obviously much smaller? 🤦‍♂️ You start wondering why it doesn’t work and as time goes by you come up with an idea, that definitely the constraints of your cell must be somehow wrong. So you check them, it won’t take long as you only have two types of cells. 🤨

text saying three hours later

Never mind, constraints are fine. Although I will try UICollectionViewFlowLayout I am sure it will not work either! 😈 After a few minutes you find out that it really doesn’t work! 🎉 Oh wait, I forgot to set estimatedItemSize on layout, well try again. “Shit it works now, how is that possible?!” 😡

Prototype cells, please save me once again! 🙏

Back in old days when autolayout was considered a witchcraft 🧙‍♀️ and UICollectionView was a newborn baby 👶 prototype cells pattern was quite common and it was pretty reliable so I guess it could save me once again.

https://youtu.be/xcgo7tMQ1A4

So you start implementing prototype cells – basically the idea is that for each cell type you keep one cell that is not visible, you configure it with content that you want displayed and you call systemLayoutFitting(...) on it. And the result size is then returned in preferredLayoutAttributes(...) of your specific cell.

You are aware that you will lose some performance 😞 but if that means reliable results it can be worth it, later on you can implement some caching and speed it up a lot. ☝️ So you implement prototype cells, test it on your typical dataset and everything works like a charm. 💪

So you don’t wait a second and create a pull request so you can show everyone how awesome you are 😎 because that’s just the way it is. 😃 It takes like 5 minutes after creating the PR until you receive Slack message “Hey man 👋 I am not sure your fixes work well, see that cell? It has clipped content. And see that cell? It has some extra insets.”

Now there is a good time to smash a few things 🔨, swear for a while 🤬, get a coffee ☕️ or a drink 🥃, or do whatever can calm you down.

I can do it better myself 👍

Okay, modesty aside. Screw fixing this layout, I can surely do it better without that non-obvious issue, obviously. 💪 So you start writing your own layout from scratch. You find several articles about creating custom layouts, you read them carefully but mostly they talk about creating custom layouts as UICollectionViewFlowLayout subclass which is not what you want as you need full control over your layout items.

Well, so you return to the basics and open Apple’s documentation for customizing collectionView layouts. This documentation is as basic as it can be so it is practically useless for our case as those example layouts do not support self-sizing. 💩

So, as there is mostly no relevant documentation you try yourself with the limited knowledge you were able to acquire online. It goes rather well as you implement all the poorly documented methods of UICollectionView layouts. You implement all the tips you can find on StackOverflow and you are finally ready to test your layout. 🤩

[PREFERRED_LAYOUT_ATTRIBUTES] ==== START ====
[PREFERRED_LAYOUT_ATTRIBUTES] IndexPath: 0 - 0
[PREFERRED_LAYOUT_ATTRIBUTES] Original: (0.0, 0.0, 428.0, 200.0)
[PREFERRED_LAYOUT_ATTRIBUTES] Preferred: (0.0, 0.0, 428.0, 200.0)
[PREFERRED_LAYOUT_ATTRIBUTES] ==== END ====

Surprisingly your brand new wonderful layout has the same issue as the one you are trying to fix, the first layout returns incorrect size for your cells! How is that possible? 😤 How can UICollectionViewFlowLayout work? 🤔 So you smash a few things again 🔨, get another coffee ☕️  or another drink 🥃 and you swear that you never want to see UICollectionView or UIKitor iOS development again. 😃

Maybe I am not the best and few things can surprise me 😳

After that continuous failure you finally start to doubt yourself 🤷‍♂️ which might be a good thing after all as it means another round of googling because it is not possible that no one ever solved this issue. After a few hours of googling you find out that everyone solves this issue by adding a delegate to layout and letting the user of the layout deal with it somehow. As your layout has to be simple to use you cannot afford this solution, you have to be able to size cells correctly using autolayout.

So you continue searching for any useful information and finally you have some luck! 🍀 You find this article where the author is dealing with similar issues as you are. So you look at his layout, implement his hacks and workarounds and are curious if it will finally work.

So you implement everything interesting you find in the article and in source code. Obviously it doesn’t work it has the same self-sizing issue as your layout has:

[PREFERRED_LAYOUT_ATTRIBUTES] ==== START ====
[PREFERRED_LAYOUT_ATTRIBUTES] IndexPath: 0 - 0
[PREFERRED_LAYOUT_ATTRIBUTES] Original: (0.0, 0.0, 428.0, 200.0)
[PREFERRED_LAYOUT_ATTRIBUTES] Preferred: (0.0, 0.0, 428.0, 200.0)
[PREFERRED_LAYOUT_ATTRIBUTES] ==== END ====

And another round of smashing things can begin. 🔨 😃

Desperate times call for desperate measures 😩

Well, since the article you were reading a few smashed things earlier was inspired by Airbnb’s MagazineLayout, you decide to look directly at its source code. The first impression is like "What the hell is that? Why does my layout have 200 lines and this has like thousands?! 😱 This impression makes you seriously realize that this issue might be much bigger than you think and you become genuinely desperate. 🔫

gif with dog flying in space rocket saying I have no idea what I am doing

So you come up with an idea 💡

  1. I will make my app use MagazineLayout and see if it works
  2. if it does, I will look through the source code if I can find anything that would make sense that might fix my self-sizing issue
  3. if yes then I am done 🎉 if not, I will keep it in app, strip it as much as possible and adjust it to my needs
  4. problem solved ✅

You start implementing layout into your app, it is really straightforward and you are done within minutes. 👌 That is the first thing you have done in minutes and it worked for the last several [fill in your respective unit of time]. And on the first attempt you see that for the first layout iteration your cell self-sized correctly. 🤩

[PREFERRED_LAYOUT_ATTRIBUTES] ==== START ====
[PREFERRED_LAYOUT_ATTRIBUTES] IndexPath: 0 - 0
[PREFERRED_LAYOUT_ATTRIBUTES] Original: (0.0, 0.0, 428.0, 200.0)
[PREFERRED_LAYOUT_ATTRIBUTES] Preferred: (0.0, 0.0, 428.0, 62.0)
[PREFERRED_LAYOUT_ATTRIBUTES] ==== END ====

To the next step – look through the source code if you can find a reason that the layout works. 🕵️‍♂️ As mentioned earlier the layout with its support classes has like thousands of lines so this might take a while.

text from sponge bob saying eight hours later

Okay I’ve spent an eternity in debugger, with debug prints and I literally have no idea how is it possible that it works. 🤦‍♂️ This approach will not be an option so we need to go to the next step.

So you start stripping the layout. ✂️ Some parts are clear, some parts not, but thanks to git you are pretty nicely able to get to something that looks almost like minimal working example. 💪 The dark side of this is that the minimal working example is still pretty big (especially compared to your original layout). But now it is time to adjust it to your needs. That is easier said than done as you are still given pretty complex code from a stranger so you almost cannot get any support, but you cannot give up now. 💪

text from sponge bob saying one eternity later

So you decide you are not able to adjust your layout because it is really tied to what it does and that is quite different to what you are trying to achieve. 😬 So what now? That is a billion dollar question and the answer? As usual smash a few more things, hopefully you still haven’t run out of things you have – that would be a really serious issue. 😂

After you calm down a bit you will come up with another idea 💡 It is amazing that after so much pretty much useless effort you are still capable of having new ideas! You can try to break MagazineLayout to misbehave in the same way your layout doesn’t work. 💥 This should definitely be much easier as breaking something is not as complicated as creating something that will work, right?

So basically you are now commenting out code that you think might have something to do with self-sizing. Typically, you try to comment out various invalidations in layout but it still does work. As the time goes by, you start commenting out parts that should have nothing to do with self-sizing, but one day you find it! 👏

newItemLayoutAttributes[itemLocation]?.zIndex = numberOfItems - itemIndex

So you apply this zIndex thing to your layout attributes which takes literally seconds and voilá your layout is sizing perfectly! 🕺

[PREFERRED_LAYOUT_ATTRIBUTES] ==== START ====
[PREFERRED_LAYOUT_ATTRIBUTES] IndexPath: 0 - 0
[PREFERRED_LAYOUT_ATTRIBUTES] Original: (0.0, 0.0, 428.0, 200.0)
[PREFERRED_LAYOUT_ATTRIBUTES] Preferred: (0.0, 0.0, 428.0, 62.0)
[PREFERRED_LAYOUT_ATTRIBUTES] ==== END ====

Now  you sort of have mixed feelings as you are obviously more than happy that after such a long time you were able to resolve the self-sizing issue 💪, but on the other hand you are in rage 😤 and full off Apple hatred 🤬 that it is not documented anywhere!

And by learning stuff the hard way like this is what really makes you an expert. 💪 😂 But still, it would be nice if we could find stuff like this easier and faster than trying it out during development. 🤷‍♂️ So take care and have enough stuff around to smash while creating your apps. 🙂

Jakub Olejník
Jakub Olejník
iOS DeveloperKuba is historically the first winner of Ackee FIFA league, chief BMW enthusiast and minor open source enthusiast.

Are you interested in working together? Let’s discuss it in person!

Get in touch >