DEV Community

loading...
Cover image for Easier SwiftUI animations with AnimatedState property wrapper.

Easier SwiftUI animations with AnimatedState property wrapper.

Daniel Tavares
Creative, Curious, Keen to Learn
・2 min read

To animate something SwiftUI you have a couple of choices, implicit and explicit animations. Implicit animations are done view view modifiers .animation(.easeIn) and explicitly ones are declare inside the animation block withAnimation.

I often use explicit animation and therefore I am always setting the values of a State property inside the animation block.

struct ExplicityAnimationExample: View {
    @State var scaleSquare = false
    var body: some View {
        Rectangle()
        .fill(Color.red)
        .frame(width: 100, height: 100)
        .scaleEffect(scaleSquare ? 2 : 1)
        .onAppear {
            withAnimation(.spring()) {
                scaleSquare.toggle()
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The same thing can be achieved by implicitly adding the animation after the scaleEffect modifier

struct ExplicityAnimationExample2: View {
    @State var scaleSquare = false
    var body: some View {
        Rectangle()
        .fill(Color.red)
        .frame(width: 100, height: 100)
        .scaleEffect(scaleSquare ? 2 : 1)
        .animation(.spring())
        .onAppear {
            scaleSquare.toggle()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This saves you wrapping the content inside the withAnimation block.

The more views you have that require animation the more state changes and view modifiers you will need.

struct ExplicityAnimationExample3: View {
    @State var scaleSquare = false
    @State var scaleSquare2 = false
    var body: some View {
        VStack(spacing: 150) {
            Rectangle()
            .fill(Color.red)
            .frame(width: 100, height: 100)
            .scaleEffect(scaleSquare ? 2 : 1)
            .animation(.spring())

            Rectangle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .scaleEffect(scaleSquare ? 2 : 1)
            .animation(.spring().speed(0.5))
        }

        .onAppear {
            scaleSquare.toggle()
            scaleSquare2.toggle()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

I find it particular difficult to maintain this is in a nice way, due to the fact that you change state in one place and you have to find the the corresponding animation that matches said change.

So I created a new property wrapper to solve this problem.

I call it AnimatedState.

You can declare the state and animation that you want like so:

@AnimatedState(
        value: false,
        animation: .spring()
    ) var scaleSquare
Enter fullscreen mode Exit fullscreen mode

Now if you change that value it will automatically animate with the given animation.

Here is the final version

struct AnimatedStateExample: View {
    @AnimatedState(
        value: false,
        animation: .spring()
    ) var scaleSquare


    @AnimatedState(
        value: false,
        animation: .spring().speed(0.5)
    ) var scaleSquare2

    var body: some View {
        VStack(spacing: 150) {
            Rectangle()
            .fill(Color.red)
            .frame(width: 100, height: 100)
            .scaleEffect(scaleSquare ? 2 : 1)

            Rectangle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .scaleEffect(scaleSquare2 ? 2 : 1)
        }

        .onAppear {
            scaleSquare.toggle()
            scaleSquare2.toggle()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This eliminate the animation modifier for each square or saves you writing the with animation block every time you need to change state.

Hope this is useful for people.

AnimatedState Gist
Animating Views and Transitions

Discussion (0)