Ever noticed that Apple’s Photos app is changing its headers-in-section to blur when they become sticky ?
As you can see, once the header-in-section has reached its sticky position, the background is changing to blur.
If you’re using Objective-C there are pretty neat solutions using VlaueForKey
What we need is visibleCells property but for visibleHeadersInSection…
For the sake of the example I am going to force unwrap optionals
Using scrollViewDidScroll is necessary in order to constantly (When the user scrolls) check what is the exact position of the header and change it accordingly.
The above approach looks promising but before we continue with it, let’s see what we need in order to change the header UI when it reaches sticky:
- The ability, that NOT all headers-in-section could change their color — some can and some can’t
- Once a header becomes visible, we need to store its header.frame.origin.y position because when it reaches sticky position it changes.
- We need to store the state of the header — sticky or floating — because during header dequeue, we will need to decide which color to present.
In order to achieve a simple architecture that manages the changeable headers I have decided to use a different approach than the above solution. The main reason is that I want something that is easy to debug because once scrollViewDidScroll fires up, we get what we call a “voodoo dance” and we want to be in control.
A few assumptions before we begin — you already implemented cellForRowAt and viewForHeaderInSection and all the relevant UITableView methods.
This article also assumes that you know how to:
Decouple the tableView datasource and delegate into its own controller,
decouple the set of data the represent the cell/header into its own controller.
Creating the Header Metadata Object
Let’s create the object that represent a header-in-section during runtime:
I will address the type HeaderColorChange later on
VisibleHeaderMetadata represents our header metadata during runtime and it can valuate if a header reached sticky position or if it is floating. Also, we store our corresponding section number in order to update the data object that represent the header.
Maintaining the VisibleHeaderMetadata Array
We have the object that represent the metadata and now it is time to create our visible-header-array. We will be utilizing UITableViewDelegate methods:
- willDisplayHeaderView — to add a metadata object into the array
- didEndDisplayingHeaderView — to remove a metadata object from the array
and the implementations:
dataController represent the data of the cells and headers — I will also address it later on
- Line 8 validates that the only object we will be appending to the array will be the type HeaderColorChange
- Line 9 updates our data object with the Y position during runtime.
By utilizing UITableViewDelegate methods, we are able to maintain a robust array of headers-in-section that its type, inherits only from HeaderColorChange.
At this point we have an array that holds, during run time, only the visible headers
Changing the header color during scrolling
I’ve decided that I want the header color change to be iterative—meaning, changing the color need to be according to the scroll position (and not at once):
Let’s see the code that is in charge of making it happen:
- In line 2 we invoke our handleColorChangingForVisibleHeaders method right from the scrollViewDidScroll — notice that there is no need to pass the content offset.
- In line 7 we are doing the same trick we did for HeaderColorChange, by asking from our dataController to return an object that can be cast to a specific protocol type — meaning, only if a data object conforms to ColorChangeable (will be shown later on).
- In line 9 and 13 we are validate the position of the header using the methods from VisibleHeaderMetadata.
- Line 10 and 15 are the methods that changes the header color — In line 10 we inject the color state because we want it to be iterative and in line 15 there is no need for that since the change is instant (the original color).
- Notice that in lines 10–11 we first change the color and only then changing the object state while in lines 14–15 we first change the state and only then changing the header color — The reason for that is because we want the color change to be iterative, we first need to change to a different color and only then add 0.1 (for example) to its alpha (things will be clearer inside HeaderColorChange implementation below).
Completing The Missing Objects
Our ColorChangeable protocol:
- Using POP (protocol oriented programing), we have created a protocol that has 2 properties with a default implementation that saves the original Y position of the header.
- I’ve decided that the property originalYPosition should be set to -1 in order to identify if it’s the first time we assign a value to it.
We should obviously create a concrete type that implements the above protocol.
An example project is available at the end of article
Our HeaderColorChange Object:
If a header need to be able to change its color when it reaches a sticky position it should inherit from HeaderColorChange.
We have 3 public methods:
- Method in line 4 changes the header back to its original color when it is floating — at once.
- Method in line 8 changes the header to its sticky color but if you recall, we are changing the color iteratively to the scrolling position of the user thus, if the object state is floating it means that it is the first time the header reached its sticky position thus, we need to change to the new color first and next time when the state will be sticky, we will just need to add 0.1 to it’s alpha
- Method in line 13 handles the dequeue part: when a header dequeues, we first evaluate the corresponding data object state — if it’s floating it must be coming from below, therefore, we need to change it to the original color, else, it must be coming from above and its color must be a full alpha (1) color.
Notice that in line 2 we have created an IBOutlet that we should connect from interface builder to this file and for every header that inherits from HeaderColorChange — we can achieve that by switching from Automatic to Manual and choose HeaderColorChange and ctrl + drag like always:
Our HeaderColorChange extension (for the colors):
Let’s take a look at iterativeBackgroundChange in line 23, we first take the old color (line 24), extract its colors including the alpha (line 26) and returning the same color but with an addition of 0.1 to its alpha giving us the desired effect.
- (Obviously) The TableController holds an instance of the type DataController in order to get the relevant data for the table view delegate and datasource.
- The imaginary type SomeEntity can be whatever you decide it to be but, if it conforms to the ColorChangeable protocol (line 5), it means that this object has a corresponding header in the table view that should change and save its color state which is exactly what we do.
You can find the example project in here.
Hope you enjoyed it like I did 🦄
If you have any question/comment/enlightenment or anything related I’d be happy to discuss it with you on Twitter or even here at the comments section.
Don’t hesitate :)