This was originally published to CocoaCovers.com in March 2014. That website is now deceased, so I am re-posting the content (this single post) to benpackard.org.
Tweetbot is a Twitter client by Tapbots. It is full of thoughtful, playful interactions. One of my favorite details is the zooming header image effect when you scroll a user’s profile:
It’s a neat way to handle the extra canvas exposed by the elasticity of the scrolling behavior, which can otherwise look a little awkward when the uppermost content is a photo or some other visually busy element. This is especially true when a toolbar is present – Safari is a good example of the default behavior:
There’s nothing egregious about this interaction; it’s just a little clumsy-looking. A big gray box, smack in the middle of the more artfully crafted toolbar and content. Tweetbot turns this same scenario into an opportunity to delight the user.
There are three silmultaneous animations occurring as the user scrolls. First, and most obviously, the image expands to fill the available space. Note that the image maintains its aspect ratio, though – it never stretches. Also, if the background image happens to be taller than the content, the hidden edges of the photo are fully revealed before any scaling takes place.
We also have the gradual fade-out of the user’s profile information, and the un-blurring of the image. If you look carefully, the image also brightens as it is revealed – presumably to ensure enough contrast with the white text in its resting state.
Let’s start by creating two main views inside a scrollview. The ‘profile view’ will hold the circular photo and all of the user info. I’ve given it a gray background for now so its easy to identify. Below the profile view, the ‘content view’ is just a screenshot from the app since we’re not going to be implementing that part.
Next, we place the header image underneath the profile view. The trick here is to make the image its own independent view in the scroller – a sibling of the profile view, not a child. This will let us adjust it with much more freedom since it won’t be constrained by the bounds of the profile view.
Also note that I’ve used an image that is taller than the profile view so that we can test the zooming action works correctly later.
Now we will start animating the zoom. First, we need to re-position the image as the user scrolls to make sure that the photo is always vertically centered between the bottom of the toolbar and the top of the content view. Remember that the profile view doesn’t grow in height – it just moves down the page with the rest of the content. So the center of the image actually moves away from the center of the profile view as the user scrolls. Since I’m a fan of autolayout (we do exist), I added an
NSLayoutConstraint that does exactly that. For every two pixels of scrolling, one pixel of vertical adjustment is required to maintain a vertical center alignment.
Now lets get the zoom working. We can add another constraint here to set the size of the image view to match the native proportions of the image itself. Just like before, this gives us a constant to tweak as the user scrolls.
Its a start, but the image is stretching. This is easily rectified using one more layout constraint to maintain the original aspect ratio.
Next we need to blur and darken the image, and only reveal it incrementally. If we place a blurred and darkened copy of the image into the view heirarchy we can just update its transparency. I found a nice implementation of the necessary image adjustments by Brian Clark and slotted my new image into place above the real photo.
The blurry image is constrained to maintain the same position and size of the original photo. Now we can simply fade out both the profile info and the blurry image to reveal the photo.
We’re almost there. But we’re still missing that nice detail from the original – the hidden top and bottom slivers of the image should be revealed before any zooming takes place. This is accomplished by comparing the height of the image with that of the profile view and storing an ‘overlap’ value that represents how much of the photo is hidden. Only once the user has scrolled beyond that value does the scaling begin.
In the Wild
I added this same detail into my baseball app No-Hitter Alerts, though I didn’t need the blurring behavior since the image is a known quantity and doesn’t obscure the text.
The source code for this implementation can be found on GitHub. Thanks for reading!