In this code-along, we will create a simple button animation using SwiftUI. The button will switch between a gray circle and a green checkmark when tapped, with a smooth animation.

By the end, you’ll have a functional button like this:
- When tapped, the button toggles between a gray circle and a green checkmark.
- The transition is animated using SwiftUI’s
withAnimation
andsymbolEffect
.
Let’s get started!
Step 1: Set up your project
- Open Xcode: Launch Xcode and select Create a new Xcode project.
- Choose Template: Select App under the iOS tab and click Next.
- Name Your Project: Enter a name for your project, like
ButtonAnimation
. Choose SwiftUI for the interface and Swift for the language. Click Next, and then save your project.
When you open your project, you’ll see the following default code in ContentView.swift
:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
Let’s break down this code:
import SwiftUI
: This line imports the SwiftUI framework, which provides the tools for building user interfaces.struct ContentView: View { }
: Defines a view called “ContentView” that conforms to theView
protocol. In SwiftUI, all on-screen elements must conform to this protocol.var body: some View { }
: Thebody
property describes the content of the view. It must return a value that conforms to theView
protocol. Thesome
keyword tells Swift that the exact type of view returned will be inferred.VStack
: A vertical stack layout container that arranges its child views vertically, one on top of the other.Image(systemName: )
: Displays a system icon (the globe).Text
: Displays a text string, here “Hello, world!”.imageScale(.large)
,foregroundStyle(.tint)
: Modifiers that change the appearance of theImage
view.padding()
: Adds space around the content of theVStack
.#Preview
: Provides a live preview of the view in Xcode’s canvas, allowing you to see the UI changes in real-time.
This code demonstrates the basic structure of a SwiftUI view, including the use of a layout container (VStack
), system images, text, and modifiers. It also showcases the live preview feature, which is essential for rapid prototyping and development in SwiftUI.
We’ll replace the default layout with our custom slider app. To begin, remove the following code from within the body
property:
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
For the label, we would like to use a checkmark with a circle around. That is a great opportunity to explore the so called SF Symbols.
Step 2: Exploring SF Symbols
SF Symbols provides thousands of consistent, highly configurable symbols that integrate seamlessly with the San Francisco system font, automatically aligning with text in all weights and sizes. You can read more about SF Symbols on Apple’s developer page:
SF Symbols. You can also download the SF Symbols app here: Download SF Symbols 6 App This app is great to explore all the different names for the SF symbols and to search for SF Symbols.
You can use SF Symbols very easily by using Image(systemName: )
. With Image()
you can create image views using a specific image you can add to your app or – if you want to use one of the pre-defined SF Symbols, you have to add systemName:
. The systemName
for a checkmark with a circle around is checkmark.circle
. To change the size of an image you have to first apply the modifier resizable()
and then add a frame with the desired height by using .frame(width: 50, height: 50)
:
Image(systemName: "checkmark.circle")
.resizable()
.frame(width: 50, height: 50)
.foregroundStyle(.green)
This is the image we want to show when the image has been clicked. Otherwise it should show a gray circle. Instead of creating another image, there is a much better way to include everything in the image we just created by tracking whether the button is “pressed” or not. For that, we will create a so called @State
variable.
Step 3: Create an @State
variable
@State
triggers that this variable is being monitored and if the value changes, the view will be updated.
Declare this state variable at the top of the struct – just before var body: some View {
:
@State private var isPressed = false
How @State
works?
@State
makes the variable dynamic—wheneverisPressed
changes, SwiftUI will automatically update the UI.- We initialize it with
false
, meaning the button will start as an unpressed circle.
Step 4: Updating the Image
Next, we’ll update our Image
view. The image will dynamically change based on whether isPressed
is true
or false
.
Change the above created Image
to the following:
Image(systemName: isPressed ? "checkmark.circle" : "circle")
The systemName
parameter dynamically selects the appropriate symbol based on the value of isPressed
:
- If
isPressed
istrue
, it shows"checkmark.circle"
. - If
isPressed
isfalse
, it shows"circle"
.
This notation inside the Image
is called “ternary conditional operator” and always looks like this:
condition ? valueIfTrue : valueIfFalse
which means: If the condition is true, the value valueIfTrue
will be used, if the condition is false, then the value valueIfFalse
will be used. It is quite a nice concise syntax if the valueIfTrue
and valueIfFalse
are not extensively long.
Therefore
Image(systemName: isPressed ? "checkmark.circle" : "circle")
means, that the image will show the checkmark image if isPressed
is true and just an empty circle otherwise.
To visually distinguish between the pressed and unpressed states, we’ll change the color of the symbol updating the .foregroundStyle()
modifier:
.foregroundStyle(isPressed ? .green : .gray)
i.e.
- When
isPressed
istrue
, the image turns green. - When
isPressed
isfalse
, the image is gray.
Step 5: Add a smooth transition with contentTransition
We want the symbol to animate smoothly when switching between the circle and checkmark. To do this, we use the .contentTransition()
modifier with a symbolEffect
.
.contentTransition(.symbolEffect)
This adds an animation effect to the change of the symbol, making the transition visually smooth.
Step 6: Detect user taps with onTapGesture
To toggle the button when it’s tapped, we add the onTapGesture
modifier. Inside it, we toggle the value of isPressed
using withAnimation
to make the change animated:
.onTapGesture {
withAnimation {
isPressed.toggle()
}
}
When the button is tapped, isPressed.toggle()
reverses its value. The toggle
method flips a boolean between true
and false
. Enclosing this within withAnimation
ensures that the visual change occurs smoothly.
Step 8: Test your app
Now that your app layout is polished, it’s time to see it in action!
- Inspect the preview in the right-hand side panel of Xcode. If the preview is not visible, click the Resume button in the canvas toolbar.
- Run your app by selecting a simulator or a physical device at the top of Xcode and pressing the Play button (the right-pointing triangle).
- Tap the button to see the circle transition to a green checkmark and back!
Congratulations!
Congrats! You’ve created your first fully functional app! This simple yet powerful app is a great example of how small steps in SwiftUI can yield quick results.
What you have learned
In this code-along, you’ve learned:
- How to declare and use an
@State
variable to dynamically track the button’s state. - How to use SF Symbols with
Image(systemName:)
to create icons. - How to resize and style images using
.resizable()
,.frame()
, and.foregroundStyle()
. - How to create interactive buttons using
onTapGesture
and toggle the state. - How to animate changes with
withAnimation
andcontentTransition(.symbolEffect)
.
This small project shows how simple changes in SwiftUI can yield dynamic and interactive effects. Keep experimenting with more animations and styles to enhance your UI design!
Keep learning, keep building, and let your curiosity guide you. Happy coding! ✨
“Stay hungry. Stay foolish.” — Steve Jobs
Download the full project on GitHub: https://github.com/swiftandcurious/ButtonAnimation.git