SwiftUI provides two types of animation: implicit and explicit. Both methods allow you to animate and transition views. To implement implicit animation, the framework provides a modifier called animation. You can attach this modifier to the view you want to animate and specify the preferred animation type. (Optional) You can define the duration and delay of the animation. SwiftUI will then automatically render the animation based on the state of the view. Explicit animation provides more limited control over the animation you want to render. Instead of attaching modifiers to the view, tell SwiftUI that you want to set the animation’s state changes within the **withAnimation()** block.

An implicit animation

Take a look at the figure above. This is a simple clickable view consisting of a red circle and a heart. When the user clicks on the heart or circle, the color of the circle changes to light gray and the color of the heart changes to red. At the same time, the size of the heart icon will increase. So we make various state changes here:

  1. The color of the circle changes from red to light gray.
  2. The heart icon changed from white to red.
  3. The heart icon doubles its original size.

If you used SwiftUI to implement a clickable circle, the code would look like this:

// // ContentView.swift // ContentView // // Created by Apple on 2021/2/5. // import SwiftUI struct ContentView: View { @State private var circleColorChanged = false @State private var heartColorChanged = false @State private var heartSizeChanged = false var body: some View { ZStack { Circle() .frame(width: 200, height: 200) .foregroundColor(circleColorChanged ? Color(.systemGray5) : .red) Image(systemName: "heart.fill") .foregroundColor(heartColorChanged ? .red : .white) .font(.system(size: 100)).scaleEffect(heartSizeChanged? 1.0:0.5)} //.animation(.default).animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)). OnTapGesture {self. CircleColorChanged. Toggle () the self. HeartColorChanged. Toggle () the self. HeartSizeChanged. Toggle ()} } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }Copy the code

We defined three state variables to model the state by setting the initial value to false. To create the circle and heart, we use ZStack to overlay the heart image on top of the circle. SwiftUI has an onTapGesture modifier that detects a tap gesture. You can attach it to any view to make it clickable. In the onTapGesture closure, we switch states to change the appearance of the view.

.animation(.spring(Response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)).animation(.spring(Response: 0.3, dampingFraction: 0.3, blendDuration: 0.3))Copy the code

This will render a spring-based animation, giving the heart a jolting effect. You can adjust the damping and mixing values to get different results.

Explicit animation

Let’s see how to use explicit animation to get the same result. As mentioned earlier, you need to wrap the state changes in a withAnimation block. To create the same animation, write the following code:

ZStack { Circle() .frame(width: 200, height: 200) .foregroundColor(circleColorChanged ? Color(.systemGray5) : .red) Image(systemName: "heart.fill") .foregroundColor(heartColorChanged ? .red : .white) .font(.system(size: 100)).scaleEffect(heartSizeChanged? 1.1:0.5)}.ontapGesture {withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {self. CircleColorChanged. Toggle () the self. HeartColorChanged. Toggle () the self. HeartSizeChanged. Toggle ()}}Copy the code

We’re no longer using the animation as opposed to the code that we summarized and the onTapGesture has a withAnimation and the withAnimation call takes the animation parameter.

With explicit animation, you can easily control the state you want to animate. For example, if you do not want to animate the size of the heart icon, you can exclude the line withAnimation from the following code:

.ontapGesture {withAnimation(.spring(Response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {self. CircleColorChanged. Toggle () the self. HeartColorChanged. Toggle ()} self. HeartSizeChanged. Toggle ()}Copy the code

In this case, SwiftUI will only animate the color changes of the circle and heart. You will no longer see the heart icon animated.

Use RotationEffect to create a load indicator

The power of SwiftUI animation is that you don’t have to worry about how to animate the view. What you need to provide is the start and end status. SwiftUI then finds the rest of the content. If you understand this concept, you can create various types of animations.

For example, let’s create a simple load indicator. To create a load indicator like the one above, we can start with an open circle like the one below:

import SwiftUI struct ContentView: View { @State private var isLoading = false var body: some View { ZStack { Circle() .stroke(Color(.systemGray5), lineWidth: 14) .frame(width: 100, height: 100) Circle().trim(from: 0, to: 0.2). Stroke (Color. Green, lineWidth: 7). 100) .rotationEffect(Angle(degrees: isLoading ? 360 : 0)) .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) .onAppear() { self.isLoading = true } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }Copy the code

The load indicator does not have to be circular. You can also use Rectangle or RoundedRectangle to create metrics. But you can change the value of the offset to create the animation without changing the rotation Angle.

To create the animation, we superimposed two rounded rectangles. The top rectangle is much shorter than the bottom one. When loading begins, we update its offset from -110 to 110.

import SwiftUI

struct ContentView: View {
 
    @State private var isLoading = false
 
    var body: some View {
        ZStack {
 
            Text("Loading")
                .font(.system(.body, design: .rounded))
                .bold()
                .offset(x: 0, y: -25)
 
            RoundedRectangle(cornerRadius: 3)
                .stroke(Color(.systemGray5), lineWidth: 3)
                .frame(width: 250, height: 3)
 
            RoundedRectangle(cornerRadius: 3)
                .stroke(Color.green, lineWidth: 3)
                .frame(width: 30, height: 3)
                .offset(x: isLoading ? 110 : -110, y: 0)
                .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
        }
        .onAppear() {
            self.isLoading = true
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Copy the code

This will move the green rectangle along the line. And, when you repeat the same animation over and over again, it becomes a load animation. The figure below illustrates the offset values.