SwiftUI: Add a nice header skew animation to your views

Martin Albrecht
ITNEXT
Published in
3 min readOct 5, 2021

--

Photo by Jonas Denil on Unsplash

During some research about typography I discovered an interesting UI effect on one of the sites I visited of which I thought it could perfectly be adopted in SwiftUI:

Header animation on https://typegeist.org/
Header animation on https://typegeist.org/

Scrolling the page skews, shades and rotates the header very nicely as if it would be on some kind of cube.

While this animation is of course implemented in JS/CSS for the web, it should be pretty easy to adopt in Swift with SwiftUI and create a nice effect for any view with some kind of header section.

The goal

Our final goal is to have some kind of article view similar to the web version, with a header section followed by some plain text:

For the sake of simplicity we just use a long dummy text and pick some image from https://unsplash.com for the header background.

Behind the scenes we’ll use a Stack wrapped inside a ScrollView, in which we’ll measure the scroll position with a GeometryReader.

The effect itself is actually quite easy and consists of three things:

  1. Rotation of the image
  2. Vertical movement of the image in sync with the applied rotation
  3. Some shading to simulate a shadow for a more authentic 3D effect

The rotation

For the calculation of the rotation we extend the Angle structure with two new functions:

  1. offset(height: CGFloat, inset: CGFloat)
  2. rotation(from: GeometryProxy)

The first function calculates an offset to translate the amount scrolled to the rotation itself, to make sure the animation is not too fast. It’s basically the calculation of 1% of the current height of our view. We could use absolute values here, but this would limit the functionality to only support one static height of the header container. With this approach the header can have any height and the translation is dynamic as it uses relative values.

The second function is calculating and returning the actual rotation. We first make use of our previously defined function to calculate the scroll offset, then we calculate the offset for the scroll translation itself.

The last step is to calculate the value for the rotation. If the user has scrolled the view, we return the degrees for the scroll value multiplied by the offset we calculated before.

Taking the plain values would result in a rotation much too fast and not aligned nicely to the top edge of the view.

The scroll offset

Next, we extend the GeometryProxy structure with a simple function to calculate the scroll position of the GeometryReader inside the ScrollView. Without this it would not be possible to measure the amount the user has scrolled the view.

Calculate the offset the ScrollView is scrolled

The shading

The last part for the effect is to add some shadow. For that we add a simple function to the header view:

The complete view

As mentioned before we build our article view by basically wrapping a VStack inside a Scrollview. Ignoring the large dummy text at the beginning, we have our extensions from before beginning at line 42.

Line 65 marks the beginning of the header view itself, separated from the main content view in line 114. As said before we just basically wrap a ZStack inside a GeometryReader and fill it with our image, a title and subtitle.

The ZStack is required to the shading which is realized by some Rectangle (line 93) which makes use of our shading function from before.

The header view itself is implemented in line 118 in our ContentView. Underneath we just add the dummy text .

And that’s pretty much everything. With this we’ll get the effect we intended in the beginning.

--

--