Animating SF Symbols in SwiftUI

SF Symbols are a natural choice for icons in SwiftUI apps. The system provides a very large symbol catalog, and extensive customization options. Size, weight, rendering mode, and color can all be adjusted to match the surrounding UI, making symbols easy to integrate across an app.

Beyond static icons, SF Symbols also support animation. These animations are called symbol effects. They allow icons to respond to state changes and interactions, bringing more life to the interface without custom drawing or complex animation code.

Symbol effects are provided by the Symbols framework, which is implicitly available when working with SwiftUI. There is a built-in collection of effects that can be applied to any symbol, including custom symbols. Effects are configurable, and options can be chained to create very specific behavior. The SF Symbols app documents each effect and its supported options.

In this post, we'll explore the SwiftUI APIs for animating SF Symbols and use the groupings based on protocol conformance in the Symbols framework as a mental model. We'll look at four effect groups: indefinite effects (IndefiniteSymbolEffect), discrete effects (DiscreteSymbolEffect), content transitions (ContentTransitionSymbolEffect), and transitions (TransitionSymbolEffect). Each group represents a distinct animation intent and naturally aligns with the SwiftUI API patterns used to apply these animations.

# Indefinite symbol effects

Indefinite symbol effects apply a modification that remains in place for as long as the effect is active. This category includes the largest set of effects, such as ScaleSymbolEffect, RotateSymbolEffect, BreatheSymbolEffect, and many others. The full list of animations that conform to the IndefiniteSymbolEffect protocol can be found in the Apple documentation.

Indefinite effects can be configured using the symbolEffect(_:options:isActive:) modifier, where the isActive parameter is used to turn the effect on or off.

Such effects can help provide feedback for a continuous user interaction, like an active hover, for example. Here is how we can scale symbols up when the mouse is over them.

struct HoverScalingSymbol: View {
    let systemName: String
    
    @State private var isHovering = false
    
    var body: some View {
        Image(systemName: systemName)
            .symbolEffect(.scale.up, isActive: isHovering)
            .onHover { hovering in
                isHovering = hovering
            }
    }
}
Animated SF Symbols showing document actions scaling up on hover

Some animations in the indefinite effects group, such as breathe, bounce, pulse, and rotate, repeat forever while active. These are great for indicating an ongoing activity or process.

Image(systemName: "record")
    .symbolVariant(.circle)
    .symbolEffect(.breathe, isActive: isRecording)
Record symbol with a continuous breathe animation

If we omit the isActive parameter for such effects, it will default to true and the animation will be active forever.

Indefinite effects really shine with configuration options that control how the animation progresses and repeats over time. By chaining these options, we can arrive at a variant that best suits our use case. Here is an example of how the variable color animation can be customized:

Image(systemName: "waveform")
    .symbolEffect(.variableColor.iterative.reversing)
Animated waveform symbol with a looping variable color effect that progresses and reverses across layers

# Discrete symbol effects

Discrete symbol effects perform a transient, one-off animation that is triggered by a change in a specific value. These effects are great for drawing attention to an element in the UI or indicating that an action has taken place.

A few animation types that support indefinite behavior also support discrete behavior. The full list can be found in the conforming types of the DiscreteSymbolEffect protocol.

To add a discrete animation to a symbol in a SwiftUI app, we can use the symbolEffect(_:options:value:) modifier. Here is how we can trigger a one-off bounce effect every time a value is incremented:

struct BasketView: View {
    @State private var numOfItems = 0
    
    var body: some View {
        VStack(spacing: 30) {
            Image(systemName: "basket")
                .symbolEffect(.bounce, value: numOfItems)
            
            Button("Add to basket") {
                numOfItems += 1
            }
        }
    }
}
Basket SF Symbol performing a brief bounce animation when the Add to basket button is clicked

To customize how the effect is performed, how fast and how many times, we can also provide the options parameter. For example, we might want to bounce the symbol twice very quickly.

Image(systemName: "basket")
    .symbolEffect(
        .bounce,
        options: .repeat(2).speed(2),
        value: numOfItems
    )
Basket SF Symbol with a brief double bounce animation triggered by the Add to basket button

# Symbol effect content transitions

When we want to switch between symbol variants or distinct symbols, we can take advantage of built-in symbol animations with the symbol effect content transitions.

When a symbolEffect content transition is applied without any configuration options, the system will choose the most appropriate transition for the context.

In the example below, the slash will be drawn on and off as we toggle the setting.

struct NotificationButton: View {
    @State private var notificationsEnabled = true
    
    var body: some View {
        Button {
            notificationsEnabled.toggle()
        } label: {
            Image(systemName: "bell")
        }
        .buttonStyle(.borderless)
        .symbolVariant(notificationsEnabled ? .none : .slash)
        .contentTransition(.symbolEffect)
    }
}
Bell SF Symbol transitioning between regular and slashed variants using a symbol effect content transition

When switching between unrelated symbols the system will do its best to interpolate between the two.

struct WeatherSymbol: View {
    @State private var isSunny = true
    
    var body: some View {
        Image(systemName: isSunny ? "sun.max" : "cloud")
            .contentTransition(.symbolEffect)
            .onTapGesture {
                isSunny.toggle()
            }
    }
}
Full sun SF Symbol transitioning to a cloud symbol and back again

We can still customize the transition with parameters passed to the symbolEffect(_:options:) function, for example .contentTransition(.symbolEffect(.replace.upUp)), but I've found that the defaults work best for many cases.

# Symbol effect transitions

When adding a symbol to the hierarchy, or removing it, we can provide a symbol effect transition using the transition(_:) modifier.

if isSnowing {
    Image(systemName: "snowflake")
        .transition(.symbolEffect)
}
Snowflake symbol transitioning in and out of view

Note, that unlike other types of transitions, the symbolEffect transition will be activated even if we don't apply an implicit animation to the view hierarchy with the animation(_:value:) modifier or wrap the state modification into a withAnimation {} function.

Instead of relying on the default behavior of the symbol effect transition, we can provide an explicit effect type to achieve a more interesting animation. For example, on iOS and macOS 26, we can set the drawOn as a preference and it will be activated for symbols that support this new animation type.

if isWindy {
    Image(systemName: "wind")
        .transition(.symbolEffect(.drawOn))
}
Wind symbol transitioning in and out with the draw on and draw off effects

SF Symbols are very flexible, and they continue to be updated every year with new behaviors and customization options. Symbol effect animations were introduced in iOS 17, and have significantly evolved since then. It's great to see how easy it is to use these animations in our SwiftUI apps and adapt them to different interaction patterns and use cases.


If you are looking to build a strong foundation in SwiftUI, my book SwiftUI Fundamentals is a great place to start. It takes a deep dive into the framework's core principles and APIs to help you understand how it works under the hood and how to use it effectively in your projects.

For more resources on Swift and SwiftUI, check out my other books and book bundles.

SwiftUI Fundamentals by Natalia Panferova book coverSwiftUI Fundamentals by Natalia Panferova book cover

Deepen your understanding of SwiftUI!$35

The essential guide to SwiftUI core concepts and APIs

SwiftUI Fundamentalsby Natalia Panferova

  • Explore the key APIs and design patterns that form the foundation of SwiftUI
  • Develop a deep, practical understanding of how SwiftUI works under the hood
  • Learn from a former Apple engineer who worked on widely used SwiftUI APIs

Deepen your understanding of SwiftUI!

The essential guide to SwiftUI core concepts and APIs

SwiftUI Fundamentals by Natalia Panferova book coverSwiftUI Fundamentals by Natalia Panferova book cover

SwiftUI Fundamentals

by Natalia Panferova

$35