Shake Animation with Animatable in SwiftUI
Animatable
protocol describes how to animate a view with respect to some change in the view’s data. We use animatable
when we want to create custom animation.
Animatable
protocol gives us fine-grained control over the animation of a SwiftUI view’s animatable values. It does so by requiring animatableData: AnimatableData
, which represents a view’s animatable data.
By conforming to Animatable
, we are able to decouple the animation of our view from the concept of duration, as we give SwiftUI the ability to interpolate arbitrarily between two different values for animatableData
. This is also the reason why AnimatableData
must conform to VectorArithmetic
, which provides the runtime means to add, subtract and scale the animated values as necessary to generate data points for each frame of the animation over an arbitrary time interval.
We will build custom shake animation by conforming to AnimatableModifier
, which is a combination of Animatable
and ViewModifier
.
Let’s start with a struct which will conform to AnimatableModifier
protocol.
struct Shake: AnimatableModifier {
}
Next, we will create body property along with a variable to take number of shakes as input and animatableData
computed property to get and set the value of number of shakes.
struct Shake: AnimatableModifier {
var shakes: CGFloat = 0
var animatableData: CGFloat {
get {
shakes
} set {
shakes = newValue
}
}
func body(content: Content) -> some View {
}
}
Note that we are creating
shakes
andanimatableData
as CGFloat not Int this is because shake represents the progress of the animation. SwiftUI runtime sets this value throughanimatableData
, and it can be any value between the initial and the final value.
For shake effect, we will offset
provided content in x axis using swift’s sin
function.
The Swift sin() function returns trigonometric sine of an angle (angle should be in radians). The returned value will be in the range -1 through 1.
struct Shake: AnimatableModifier {
var shakes: CGFloat = 0
var animatableData: CGFloat {
get {
shakes
} set {
shakes = newValue
}
}
func body(content: Content) -> some View {
content
.offset(x: sin(shakes * .pi * 2) * 5)
}
}
Let’s create an extension in View
for convenience.
extension View {
func shake(with shakes: CGFloat) -> some View {
modifier(Shake(shakes: shakes))
}
}
We are ready to use this new modifier inside a view. We will create a view to take user’s input for username via TextField
view. Upon committing the value(hit return key) on TextField
we will reset number of shakes to 0.0 and use a Button
to check if the username is equal to “DevTechie”, if values don’t match, we will increase the number of shakes to 4 inside withAnimation
block.
Interpolate of number of shakes is determined by what type of
Animation
is used inwithAnimation
, to animate the change from 0.0 shakes to 4.0 shakes.
struct DevTechieShakeAnimation: View {
@State private var username: String = ""
@State private var numberOfShakes = 0.0
var body: some View {
NavigationStack {
VStack(alignment: .leading) {
Text("Enter username")
TextField("username", text: $username, onCommit: {
numberOfShakes = 0.0
})
.textFieldStyle(.roundedBorder)
.shake(with: numberOfShakes)
Button("Check") {
if username != "DevTechie" {
withAnimation {
numberOfShakes = 4
}
}
}
}
.padding()
.navigationTitle("DevTechie")
}
}
}
Build and run.
We can apply this modifier in other views as well.
struct DevTechieShakeAnimation: View {
@State private var numberOfShakes = 0.0
var body: some View {
NavigationStack {
VStack {
Text("Hey DevTechie, \nShake me!")
.padding()
.font(.largeTitle)
.multilineTextAlignment(.trailing)
.foregroundStyle(.white)
.background(.orange.gradient, in: RoundedRectangle(cornerRadius: 10))
.shake(with: numberOfShakes)
Button("Shake") {
withAnimation(Animation.easeInOut(duration: 2)) {
numberOfShakes = 10
}
}
}
.padding()
.navigationTitle("DevTechie")
}
}
}
With that we have reached the end of this article. Thank you once again for reading. Don’t forget to 👏 and follow 😍. Also subscribe our newsletter at https://www.devtechie.com