avatarDevTechie

Summary

The provided content describes how to implement a custom shake animation in SwiftUI using the Animatable protocol and AnimatableModifier.

Abstract

The article delves into the creation of a shake animation effect in SwiftUI by leveraging the Animatable protocol, which allows for fine-grained control over view animations. It explains the necessity of the animatableData computed property and its relationship with the AnimatableData conformance to VectorArithmetic. The author guides the reader through defining a Shake struct that conforms to AnimatableModifier, which includes a body property and an animatableData property to manage the number of shakes. The shake effect is achieved by offsetting the content along the x-axis using Swift's sin function. Additionally, the article provides an extension on the View protocol for ease of use and demonstrates how to apply the shake animation to a view when certain conditions are met, such as an incorrect username input. The article concludes with a call to action for readers to engage with the content by clapping, following, and subscribing to a newsletter.

Opinions

  • The author emphasizes the importance of using CGFloat for the shakes variable to allow for smooth interpolation between animation states.
  • The use of withAnimation is highlighted as a means to control the animation timing and interpolation of the shake effect.
  • The article suggests that using custom animations like the shake effect can enhance user experience by providing visual feedback.
  • The author provides a practical example of how to integrate the shake animation with user input, demonstrating its applicability in real-world scenarios.
  • The article implies that extending the View protocol with custom modifiers, such as .shake, can lead to more readable and maintainable code.
  • By encouraging reader interaction (claps, follows, newsletter subscription), the author indicates the value they place on community engagement and feedback.

Shake Animation with Animatable in SwiftUI

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 and animatableData as CGFloat not Int this is because shake represents the progress of the animation. SwiftUI runtime sets this value through animatableData, 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 in withAnimation, 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

Swiftui
iOS
iOS App Development
iOS Development
Animation
Recommended from ReadMedium