Creating a Button Animation in SwiftUI

Button.gif

Today we’re going to look at how to create a quick and easy animation for a button that scales and fades out when you tap on it.

Setting Up

For this tutorial we’re going to need two main views:

  • The button itself

  • A view for handling the animation

So to get started let’s create a simple rectangular button.

Button(action: {
    // button action
}) {
    ZStack {
        Rectangle()
            .frame(width: 120, height: 60)
            .foregroundColor(.red)
        Text("Button")
            .foregroundColor(.white)
    }
}

Animation state

Now that we’ve got a button, we need to add some way to manage its current animation state. The goal here is to have some way to notify our animation view that the button was tapped, then when the animation is done that state should reset.

An easy way to do that is to have a bool for keeping track of if the button was tapped, then after a delay that is the same duration as the animation just reset the bool. Assuming we want our animation to last for 0.5 seconds, let’s go ahead and add a state property for our button and update the action for our button to toggle that property.

@State private var didTapButton = false

Button(action: {
    self.didTapButton = true
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.didTapButton = false
    }
})

Creating the animation

OK cool, so we have a button that keeps track of if it was tapped. Now we’re ready to build out the animation for the button. The animation we’re going to use is very simple - just a rectangle that matches the button’s color and size that expands out and fades out at the same time.

Start by creating a new view called ButtonAnimation that has a binding property for the state from the main button view. You can also add in a Color property to make sure the animation’s color matches the button’s color.

struct ButtonAnimation: View {
    @Binding var isExpanded: Bool
    let color: Color

    var body: some View {
        Rectangle()
            .frame(width: 120, height: 60)
            .foregroundColor(color)
    }
}

Next up is handling the animation based on the isExpanded binding. To do that we’ll need to update two different modifiers:

  • The animation view’s alpha should go from 0.5 to 0.0 alpha so it fades out

  • The animation view’s scale should go from 1 to 2.5 so it expands

Rectangle()
    .frame(width: 120, height: 60)
    .foregroundColor(color)
    .opacity(isExpanded ? 0 : 0.5)
    .scaleEffect(isExpanded ? 2.5 : 1)

If we were to run this now you wouldn’t ever see the animation view because nothing is being told to animate. Instead, the animation view would simply toggle between its start and end state, and since its end state has an opacity of 0 it would be invisible in the expanded state.

All we need to do is tell the animation view how to animate with an animation modifier.

.animation(isExpanded ? .easeOut(duration: 0.5) : nil)

I went with an easeOut animation curve, but you can use whatever you’d like. You may have noticed that I’m also skipping animation when isExpanded is reset to false. This is because I don’t want the user to see the reverse animation resetting the animation view. Instead the animation view should just reset so it’s ready to go again without the user ever knowing.

Putting it all together

Last but not least we need to add the animation to our main button view. The animation should look like it expands out from behind the button, so go ahead and add it as the first item in the ZStack:

ButtonAnimation(isExpanded: $didTapButton, color: .red)

Now when you tap the button you should see your fancy new animation run!

tl;dr here’s the full code

struct ButtonAnimation: View {
    @Binding var isExpanded: Bool
    let color: Color

    var body: some View {
        Rectangle()
            .frame(width: 120, height: 60)
            .foregroundColor(color)
            .opacity(isExpanded ? 0 : 0.5)
            .scaleEffect(isExpanded ? 2.5 : 1)
            .animation(isExpanded ? .easeOut(duration: 0.5) : nil)
    }
}

struct ContentView: View {
    @State private var didTapButton = false

    var body: some View {
        Button(action: {
            self.didTapButton = true
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.didTapButton = false
            }
        }) {
            ZStack {
                ButtonAnimation(isExpanded: $didTapButton, color: .red)
                Rectangle()
                    .frame(width: 120, height: 60)
                    .foregroundColor(.red)
                Text("Button")
                    .foregroundColor(.white)
            }
        }
    }
}

Check out some more of my recent tutorials

Previous
Previous

Horizontal Scrolling in SwiftUI

Next
Next

Easy to Use Cell Reuse Extensions