Exploring SF Symbols in Motion: Creating Engaging Animations (Part 2)

987 words • 4 minute read.

Last week, I wrote about SF Symbols and how to use them in your apps. If you missed it, you can check it out here. This week, we’ll dive into animating SF Symbols—because who doesn’t love a good animation?

image.png

Now, according to Apple documentation (straight from the docs, not my words), there are 4 different behaviour types:

Discrete

An effect that runs from start to finish.

Indefinite

An effect that lasts until you remove or disable it.

Transition

An effect that animates a symbol in or out of visibility.

Content Transition

An effect that replaces one symbol with another symbol, or with a different configuration of itself.

This doesn’t mean to say that all animations fall under different types, we can customise each animation type to behave how we want it to.

Let’s take a look how…

Indefinite Animations

Here’s how the sun.min.fill symbol looks when animated in five different ways. Using the .symbolEffect() modifier without any options makes the animation run Indefinitely. While this is easy to implement, it’s usually not recommended to let animations run indefinitely unless you have a specific, justified use case.

Image(systemName: "sun.min.fill")
    .symbolEffect(.bounce)

Image(systemName: "sun.min.fill")
    .symbolEffect(.breathe)

Image(systemName: "sun.min.fill")
    .symbolEffect(.pulse)

Image(systemName: "sun.min.fill")
    .symbolEffect(.rotate)

Image(systemName: "sun.min.fill")
    .symbolEffect(.wiggle)

Examples of different indefinite SF Symbol animations using .symbolEffect().

Examples of different indefinite SF Symbol animations using .symbolEffect().

Discrete Animations

Discrete animations, as mentioned earlier, are an effect that run from start to finish. Below are some of the different ways that we can achieve this effect. .symbolEffect() has an options parameter which takes a SymbolEffectOptions property.

.nonRepeating - Allows the animation to run once, and once only.

.repeat(_ count: Int?) - Takes an integer as input, this specifies the number of times we want the animation to repeat for.

Image(systemName: "sun.min.fill")
    .symbolEffect(
        .wiggle,
        options: .nonRepeating
    )

Image(systemName: "sun.min.fill")
    .symbolEffect(
        .wiggle,
        options: .repeat(1)
    )

Image(systemName: "sun.min.fill")
    .symbolEffect(
        .wiggle,
        options: .repeat(3)
    )

Examples of discrete SF Symbol animations using .symbolEffect().

Examples of discrete SF Symbol animations using .symbolEffect().

Transition Animations

Transition symbol effects animate symbols in and out of visibility. While experimenting, I didn’t notice much difference between using the .transition() modifier and not using it, but maybe I’m missing something?

HStack(spacing: 50) {
    if isPresenting {
        Image(systemName: "sun.min.fill")
            .transition(.symbolEffect(.appear))
    }
}
.font(.system(size: 40))
.frame(height: 50)

Button {
    withAnimation {
        isPresenting.toggle()
    }
} label: {
    Text("Animate")
    
}
.buttonStyle(.bordered)

Using .symbolEffect(.appear) for transition animations.

Using .symbolEffect(.appear) for transition animations.

Content Transition Animations

Content Transition effects, like .replace, animate one symbol transitioning into another with a smooth scaling effect*.*. Using it this way seems to be most effective, when using the symbol inside an if statement instead of a ternary, we don’t get the same effect, instead you get the standard opacity animation when using withAnimation on it’s own.

Button {
    withAnimation {
        isSunny.toggle()
    }
} label: {
    Label("Animate", systemImage: isSunny ? "sun.max.fill" : "cloud.rain.fill")
}
.contentTransition(.symbolEffect(.replace))
.buttonStyle(.bordered)

Using .symbolEffect(.replace) for content transition animations.

Using .symbolEffect(.replace) for content transition animations.

Combining Effects

You can combine different SF Symbol effects to create customised animations. Here we have used .renderingMode(.original) to make the heart red, we have given the symbolEffect options a parameter of .repeat(3) meaning it will repeat 3 times, and we activate the animation using the isActive parameter.

HStack(spacing: 50) {
    
    Image(systemName: "heart.fill")
        .renderingMode(.original)
        .symbolEffect(
            .breathe,
            options: .repeat(3),
            isActive: animateHeart
        )
    
}
.font(.system(size: 40))

Button {
    animateHeart.toggle()
} label: {
    Text("Breathe")
    
}
.buttonStyle(.bordered)

Combining .renderingMode() and .symbolEffect() for a customised animation.

Combining .renderingMode() and .symbolEffect() for a customised animation.

Conclusion

There is a lot you can do when it comes to animating SF Symbols, and I have probably only scratched the surface here. Go experiment and see what you can come up with.

Please remember that animations are best used to help enhance the users experience, and give polished professional feel to your app. Placing them everywhere can be distracting to the user, so less can be more as the saying goes.

Thanks for stopping by 🙂.