Swift 3) – Assume And Construct


After utilizing Twitter’s iOS App for some time, I began it with the developer’s eye and observed that among the delicate actions and element interactions are extraordinarily fascinating. This sparked my curiosity: how did you guys at Twitter do it?

Extra particularly, let’s speak concerning the profile view: isn’t it elegant? It appears to be like like a default view, however when you look intently you’ll discover there’s way more. Layers overlap, scale and transfer in unison with the scrollview offset, creating an harmonic and easy ensemble of transitions… I acquired carried away, however sure you guessed it, I like it.

So, let’s do it and recreate this impact straight away!

First issues first, here’s a preview of the ultimate consequence for this tutorial:

Construction’s description

Earlier than diving into the code I wish to provide you with a quick thought of how the UI is structured.

Open the Major.storyboard file. Inside the one View Controller’s view yow will discover two fundamental Objects. The primary is a view which represents the Header and the second is a Scrollview which accommodates the profile picture (let’s name it Avatar) and the opposite data associated to the account just like the username, and the follow-me button. The view named Sizer is there simply to make certain that the Scrollview content material is large enough to allow vertical scrolling.

As you possibly can see, the construction is basically easy. Simply be aware that I’ve put the Header outdoors the Scrollview, quite than place it along with the opposite components, as a result of, though it won’t be strictly needed, it offers the construction extra flexibility.

Let’s code

Should you look fastidiously on the ultimate animation you’ll discover you possibly can handle two completely different potential actions:

1) Person pulls down (when the Scrollview content material is already on the prime of the display screen)

2) Person scrolls down/up

This second motion can in flip be cut up in 4 extra steps:

2.1) Scrolling up, the header resizes down till it reaches Navigation Bar default dimension after which it sticks to the highest of the display screen.

2.2) Scrolling up, the Avatar turns into smaller.

2.3) When the header is fastened, the Avatar strikes behind it.

2.4) When the highest of the Person’s title Label reaches the Header, a brand new white label is displayed from the underside middle of the Header. The Header picture will get blurred.

Open ViewController.swift and let’s implement these steps one after the other.

Setup the controller

The very first thing to do is clearly to get details about the Scrollview offset. We are able to simply do this by way of the protocol UIScrollViewDelegate implementing the scrollViewDidScroll operate.

The best technique to carry out a metamorphosis on a view is utilizing Core Animation homogeneous three-dimensional transforms, and making use of new values to the layer.rework property.

This tutorial about Core Animation would possibly come in useful: https://www.thinkandbuild.it/playing-around-with-core-graphics-core-animation-and-touch-events-part-1/.

These are the primary traces for the scrollViewDidScroll operate:

   var offset = scrollView.contentOffset.y
   var avatarTransform = CATransform3DIdentity
   var headerTransform = CATransform3DIdentity

Right here we get the present vertical offset and we initialize two transformations that we’re going to setup afterward with this operate.

Pull down

Let’s handle the Pull Down motion:

if offset < 0 {

     let headerScaleFactor:CGFloat = -(offset) / header.bounds.top
     let headerSizevariation = ((header.bounds.top * (1.0 + headerScaleFactor)) - header.bounds.top)/2.0
     headerTransform = CATransform3DTranslate(headerTransform, 0, headerSizevariation, 0)
     headerTransform = CATransform3DScale(headerTransform, 1.0 + headerScaleFactor, 1.0 + headerScaleFactor, 0)

     header.layer.rework = headerTransform

First, we test that the offset is destructive: it means the consumer is Pulling Down, coming into the scrollview bounce-area.

The remainder of the code is simply simple arithmetic.

The Header has to scale up in order that its prime edge is fastened to the highest of the display screen and the picture is scaled from the underside.

Principally, the transformation is made by scaling and subsequently translating to the highest for a price equal to the scale variation of the view. The truth is, you would obtain the identical consequence shifting the pivot level of the ImageView layer to the highest and scaling it.

headerScaleFactor is calculated utilizing a proportion. We wish the Header to scale proportionally with the offset. In different phrases: when the offset reaches the double of the Header’s top, the ScaleFactor needs to be 2.0.

The second motion that we have to handle is the Scrolling Up/Down. Let’s see the best way to full the transformation for the principle components of this UI one after the other.

Header (First part)

The present offset must be higher than 0. The Header ought to translate vertically following the offset till it reaches the specified top (we’ll discuss Header blur later).

headerTransform = CATransform3DTranslate(headerTransform, 0, max(-offset_HeaderStop, -offset), 0)

This time the code is basically easy. We simply rework the Header defining a minimal worth that’s the level at which the Header will cease its transition.

Disgrace on me: I’m lazy! so I’ve hardcoded numeric values like offset_HeaderStop inside variables. We may obtain the identical end in different elegant methods, calculating UI ingredient positions. Possibly subsequent time.


The Avatar is scaled with the identical logic we used for the Pull Down however on this case attaching the picture to the underside quite than the highest. The code is basically related apart from the truth that we decelerate the scaling animation by 1.4.

// Avatar -----------

let avatarScaleFactor = (min(offset_HeaderStop, offset)) / avatarImage.bounds.top / 1.4 // Decelerate the animation
let avatarSizeVariation = ((avatarImage.bounds.top * (1.0 + avatarScaleFactor)) - avatarImage.bounds.top) / 2.0
avatarTransform = CATransform3DTranslate(avatarTransform, 0, avatarSizeVariation, 0)
avatarTransform = CATransform3DScale(avatarTransform, 1.0 - avatarScaleFactor, 1.0 - avatarScaleFactor, 0)

As you possibly can see, we use the min operate to cease the Avatar scaling when the Header transformation stops (offset_HeaderStop).

At this level, we outline which is the frontmost layer relying on the present offset. Till the offset is lower than or equal to offset_HeaderStop the frontmost layer is the Avatar; larger than offset_HeaderStop it’s the Header.

           if offset <= offset_HeaderStop {

                if avatarImage.layer.zPosition < header.layer.zPosition{
                    header.layer.zPosition = 0

            }else {
                if avatarImage.layer.zPosition >= header.layer.zPosition{
                    header.layer.zPosition = 2

White Label

Right here is the code to animate the white Label:

let labelTransform = CATransform3DMakeTranslation(0, max(-distance_W_LabelHeader, offset_B_LabelHeader - offset), 0)
headerLabel.layer.rework = labelTransform

Right here we introduce two new shame-on-me variables: when offset is the same as offset_B_LabelHeader , the black username label touches the underside of the Header.

distance_W_LabelHeader is the space wanted between the underside of the Header and the White Label to middle the Label contained in the Header.

The transformation is calculated utilizing this logic: the White Label has to seem as quickly because the Black label touches the Header and it stops when it reaches the center of the header. So we create the Y transition utilizing:

max(-distance_W_LabelHeader, offset_B_LabelHeader - offset)


The final impact is the blurred Header. It took me three completely different libraries to seek out the suitable resolution… I’ve additionally tried constructing my tremendous simple OpenGL ES helper. However updating the blur in realtime all the time ended as much as be extraordinarily laggy.

Then I spotted I may calculate the blur simply as soon as, overlap the not-blurred and the blurred picture and simply play with alpha worth. I’m fairly certain that’s what Twitter devs did.

In viewDidAppear we calculate the Blurred header and we conceal it, setting its alpha to 0:

// Header - Blurred Picture

headerBlurImageView = UIImageView(body: header.bounds)
headerBlurImageView?.picture = UIImage(named: "header_bg")?.blurredImage(withRadius: 10, iterations: 20, tintColor: UIColor.clear)
headerBlurImageView?.contentMode = UIViewContentMode.scaleAspectFill
headerBlurImageView?.alpha = 0.0
header.insertSubview(headerBlurImageView, belowSubview: headerLabel)

The blurred view is obtained utilizing FXBlurView.

Within the scrollViewDidScroll operate we simply replace the alpha relying on the offset:

headerBlurImageView?.alpha = min (1.0, (offset - offset_B_LabelHeader)/distance_W_LabelHeader)

The logic behind this calculation is that the max worth needs to be 1, the blur has to start out when Black Label reaches the header and it has to cease when the white label is at its ultimate place.

That’s it!

I hope you’ve loved this tutorial (regardless of the shame-on-me variables :P). Finding out the best way to reproduce such a fantastic animation was plenty of enjoyable for me.

And poke me on Twitter in case you have any fascinating UIs you’d prefer to see x-rayed and rebuilt: we may work on it collectively! 🙂

A giant thanks goes to Nicola who has taken time to overview this text!



Leave a Comment