If, like me, you’ve found yourself needing to implement a collapsing header in your iOS app, look no further. This blog post will walk you through a simple implementation of a collapsing header in an iOS app using UIKit.
The main components are:
- Containing View Controller
- Header View
- Scroll View
Start by constructing the Containing View Controller and adding the Header.
- Add a View Controller to your storyboard, call it ContainingViewController.
- Add a subview to ContainingViewController and call it HeaderView. For the purposes of this example, we apply a static height constraint to the header view. In a production application, the height can be determined by AutoLayout based off of the constrained elements within the HeaderView.
- Add constraints to the HeaderView, leading and trailing to the SafeArea, and top to Superview.Top (note, since we want this to appear to collapse offscreen, and we don’t want a blank space above the safe area, we constrain to the top of the screen.)
Next, we add a Container View to our Containing View Controller, this will hold our UIScrollView.
- Add a ContainerView to ContainingViewController, we connect this to our UIScrollView (in this example using a UITableViewController) via an Embed Segue.
- Add constraints to the ContainerView, leading, trailing, and bottom to the safe area. We constrain the top to Superview.Top, and the constant we set here will be the collapsed height of our header.
Now that we’ve got our UI constructed, we add the code. Our ContainingViewController should look like this (note, we’ve added the IBOutlets ahead of time).
For the purposes of this example, our UIScrollView implementation is a UITableView. Note that this can be swapped out for any UIScrollView implementation.
The majority of the code in the TableViewController is boilerplate, so I’ve excluded it from this post. The important points are above. It conforms to ScrollViewContained protocol. This protocol simply defines that it will have a scrollDelegate property. The other important point is that on scrollViewDidScroll, we call the delegate’s scrollViewDidScroll method.
The two protocols are defined as follows:
Now going back to our ContainingViewController, we add a helper variable:
and we override viewDidLoad:
Two important things to note in the viewDidLoad method:
- We set the scrollView’s content inset, with the top being set to maxScrollAmount, the helper variable we just created. This adjusts the inset to account for scrolling the header offscreen.
- We set ourself (i.e. ContainingViewController) as the scrollViewContained.scrollDelegate.
But wait! Our controller doesn’t implement the ScrollViewContainingDelegate protocol! Let’s add that code now:
This delegate implementation is the important part of the code so I’ll go over it in depth. The way that this implementation works, is that as you scroll your UIScrollView, the header is scrolled up offscreen, creating the collapsing effect. In this case, we just change the header color when we’re at the top (i.e. fully collapsed state), however, for more complex UI you could animate alongside this scrolling by calculating the scroll percentage as a percentage of the maxScrollAmount variable.
We calculate the value of the newTopConstraintConstant, it is negative because we are adjusting the constraint to move the header offscreen.
We then set the constant, by taking the maximum between -maxScrollAmount and newTopConstraintConstant, and then the minimum between that value and 0. What does this accomplish? By doing this, we’re ensuring both that the header doesn’t get scrolled farther offscreen than its total height. Second, by not allowing it to be greater than zero, we’re saying that once the header is fully expanded it cannot scroll down further.
Lastly, we handle the state change of being at the top in the full collapsed state, and in this case we just change the background color.
And that’s it, you now have a functional collapsing header. If you want to take this further, it is possible to animate alongside the collapsing of the header, so that it changes as it scrolls. If you want to check out the source code for this example, it can be found here.