In this article I will talk about GeometryEffect and some techniques how this modifier can be used to spice up animations in your SwiftUI apps. If you are new to SwiftUI, i recommend you to go through set of tutorials provided by Apple that serve as a great kickstart reference.
At first glance GeometryEffect is just another modifier as others, like .rotationEffect
.offset
or .scaleEffect
but even though it can be used in the same manners there is one important difference of how animation system handles them
Lets build a sample project to demonstrate it.
Since the SwiftUI is still in beta all code provided below was working in XCode 11 beta 6
Setup
Lets create simple View with a image and slider. And lets assume we want the image to be rotated based on slider position. This is easy job to do and can be done like this using rotationEffect.
When moving the slider, it works just like we expected, the image is rotated around.
Now, lets change how the image is rotated and make it rotated only when the slider is in middle of the range.
We will adjust computation of rotation angle like this
Still looking fine, right? Lets add there two buttons that will set the offset to minimum and maximum values and wrap it in withAnimation
block.
Suddenly we see that the rotation is animated between various offsets but not in the manner we have designed and definitively not between edge positions.
This is simply because even though the sliderOffset
changes from 0 to 1, the rotation does not change - it is 0 on both edges and animation system seems pointless to interpolate between same values.
So what if we really want it to animate just like when the slider is being dragged?
As a potential solution seems to be creation of custom modifier like this and setting the offsetValue as animatable data
Sadly, soon we realize that this is dead-end
We need somethin else…
GeometryEffect comes to the scene
As you definitively guessed, the situation will be different using GeometryEffect. GeometryEffect is another modifier, but it behaves differently while animation interpolates. Its main method func effectValue(size: CGSize) -> ProjectionTransform
is called continuously during interpolation of its offsetValue
property.
Lets replicate our functionality with GeometryEffect implementation:
The return value of effectValue
method is a ProjectionTransform struct which is basically 3x3 transformation matrix various transformations (like scale, translation, rotation) that is applied on the view in visual space.
You can see in the example that even though our effect aims on the rotation, we had to introduce 2 translations. That is because the rotation is being done around point (0,0) (top-left corner) so first we shift center of the view to that point, rotate it and move back to previous position.
Update 3rd of September 2019
Since ProjectionTransform supports initialization from CATtransform3D structure, it is possible to introduce full 3D transformations of the view.
In the example below the rotation along y axis is introduced. Note setting of m34
element that sets the perspective parameter of the projection transformation. Without that the projection of the view is orthographic. Similarly as before, since the origin of the object space is in top left corner, we need to apply two translation transformations. Here the first is being done in 3D space to shift center of rotation and tha latter is affine transformation in view space.
Better use-cases
OK, but what is all of this good for?
I am using geometry effect as a addition to standard modifiers mainly in these cases:
- to introduce event-based forth and back animation
- there is need of another transformation, but only during animation
I will share examples:
Button triggered animation
This is actually the case how I got on the track of GeometryEffect. I wanted the button to perform scale operation when tapped. As you probably know, there is ButtonStyle protocol to alter appearance of the button that is usable in such cases (and it animates) but I wanted the animation to happen after the tap not depending on the tap duration.
And if you want to create eye candy animation that is applicable not just on buttons, this is the easy way.
So what about a like button?
As you can see it is just reusing of the priciples from our demo. It demonstrates the distinct effect of ButtonStyle that is changing button appearance on tap and animation being done after the tap is triggered. Notice that no animation is performed when the tap is cancelled which is yet another benefit,
Additional transformation during animation
Into this category our first example fits, but lets have a look on something different.
What about some fancy menu? Here we will demonstrate how well you can combine effect of standard modifiers with GeometryEffect. Lets have a look at the result and the code below.
Here the JumpyEffect
implementation is even simpler than before - we are just altering x
coordinate of the orange circle. Notice that the y
coordinate is controlled by standard .offset
modifier and only the jumpy part is handled by the GeometryEffect.
Such combinations in multiple axis allows us to break animation into smaller pieces than writing complex paths.
That is all for today, you can find all codes at this github repo
Update 3rd of September 2019
There is another great material about GeometryEffect by SwiftUI Lab - I really recommend reading it
Did you like this article?
Feel free to comment or criticise so the next one is even better. Or share it with other SwiftUI adopters ;)