Streaming is available in most browsers,
and in the Developer app.
-
Animate symbols in your app
Bring delight to your app with animated symbols. Explore the new Symbols framework, which features a unified API to create and configure symbol effects. Learn how SwiftUI, AppKit, and UIKit make it easy to animate symbols in user interfaces. Discover tips and tricks to seamlessly integrate the new animations alongside other app content. To get the most from this session, we recommend first watching “What's new in SF Symbols 5.”
Resources
Related Videos
WWDC23
-
Download
♪ ♪ Anant: Hello. Welcome to "Animate symbols in your app." My name is Anant, and I'm an engineer on UIKit. SF Symbols are an iconic part of Apple interfaces.
They look gorgeous in menus, toolbars, sidebars, and more. And because people are familiar with symbols, they make your app more intuitive to use. In iOS 17 and macOS Sonoma, we're enhancing symbols with animation, bringing more life into your apps than ever before.
I'll start with a tour of the new symbol animations, also called "symbol effects." Then, I'll guide you through the new APIs in SwiftUI, UIKit, and AppKit to add symbol effects to your apps. And finally, I'll give you some tips to make symbol effects really shine.
So let's jump in.
iOS 17 and macOS Sonoma introduce a collection of universal animations that can be applied to any symbol image, even custom symbols. These animations are called Bounce, Pulse, Variable Color, Scale, Appear, Disappear, and Replace.
I recommend checking out the "What's new in SF Symbols 5" session to dive deeper into the animations themselves, including best practices for designing interfaces with them.
In the API, these animations are called "symbol effects," and the new Symbols framework is home to all of them. It's included for free when you use SwiftUI, AppKit, or UIKit to build your app. A really cool feature of the Symbols framework is that each effect has a simple dot-separated name. So to create a bounce effect, you can simply write ".bounce" in your code.
These dot-separated names also extend to the way you configure effects. For example, you can specify that the symbol should bounce upwards or downwards, but most of the time, you won't need to specify anything. The frameworks will automatically use the most appropriate direction. Some effects feature many configuration options. For example, Variable Color has three different settings. By chaining options together, you can configure very specific effects with ease.
The effect names are real Swift code. There's no strings attached. Xcode will autocomplete each part of the name, and if an effect is configured incorrectly, you'll get an error at compile time. The best way to explore all the new animations is the SF Symbols app. In the new animation tab, you can learn about all the available configuration options for each effect. You can even copy a dot-separated effect name to be used directly in your code. With all of the effect types and configuration options, there's a massive variety of animations available. But all of these effects actually encompass a small set of behaviors.
Bounce, for example, plays a one-off animation on the symbol. This is considered discrete behavior. Adding a Scale effect, on the other hand, changes the symbol's scale level and keeps it there indefinitely. Scale is said to support indefinite behavior. Unlike discrete effects, indefinite effects only end when explicitly removed.
Appear and Disappear support transition behavior. They can transition the symbol in and out of view.
And finally, Replace is a content transition. It animates from one symbol to another.
So that's the four different behaviors: discrete, indefinite, transition, and content transition. In the Symbols framework, each behavior corresponds to a protocol. Effects declare their supported behaviors by conforming to these protocols.
Here is a breakdown of all available effects, as well as their supported behaviors. I'll cover this in more detail in this session. Just know that an effect's behavior determines which UI framework APIs can work with them. And speaking of UI framework APIs, let's talk about how to add all of these cool effects in your SwiftUI, UIKit, and AppKit apps.
In SwiftUI, there is a new view modifier, symbolEffect.
Simply add the modifier and pass in the desired effect. Here, I pass in variableColor, and now the symbol is playing the default variable color animation.
It's easy to do this in AppKit and UIKit too. Just use the new addSymbolEffect method on an image view to add a variable color effect. I can configure the variable color effect using the dot syntax. Here, I change the effect to variableColor.iterative.reversing, resulting in a different variable color animation. It's a great way to show that my app is connecting to the network. It's even possible to combine different effects. Here, I add a scale.up effect. Now the symbol is animating variable color while also scaled up.
These APIs provide a simple way to add indefinite effects to symbol images. Recall that indefinite effects change some aspect of a symbol indefinitely, until the effect is removed.
So using the symbolEffect modifier, I can apply a variable color effect, which continuously plays an animation.
But I also need a way to control when the effect is active. I wouldn't want this animation to keep playing after my app successfully connects to the network.
This can be done by adding the boolean isActive parameter. Here, I apply the effect only when connecting to the internet. Once the app finishes connecting, the symbol animation seamlessly ends.
In AppKit and UIKit, use the removeSymbolEffect method to end indefinite effects. What about discrete effects, which perform one-off animations? I mentioned Bounce as an example of this earlier. Your app may trigger Bounce effects in response to certain events.
In SwiftUI, I can use the same symbolEffect modifier to add discrete effects. However, I must also provide SwiftUI a value. Whenever the value changes, SwiftUI triggers the discrete effect.
Let's add a button that, when pressed, bounces the symbol. The button's handler simply needs to increment bounceValue. SwiftUI will see the change in bounceValue and trigger the bounce. I can do this in AppKit and UIKit by adding a Bounce effect to the image view. Because Bounce only supports discrete behavior, then adding the effect performs a single bounce. There's no need to remove the effect afterwards.
Now, let's say I don't want the symbol to bounce just once. How about bouncing twice? SwiftUI, AppKit, and UIKit support an options parameter, where I can specify a preferred repeat count. Now, the symbol bounces twice when the effect is triggered. Bounce isn't the only effect which can have discrete behavior. Two of the effects I covered earlier, Pulse and Variable Color, support not only indefinite behavior, but also discrete behavior. In other words, they can play one-off animations, just like Bounce. That means I can take the earlier Bounce example and change it to variableColor. Variable Color switches to use its discrete behavior, since it's applied in a non-repeating fashion.
Now, pressing the button performs two Variable Color cycles.
Next, let's talk about content transition effects. The Replace effect, which animates between two different symbol images, is the main example of this. Here, I have an image that switches between a pause symbol and a play symbol.
SwiftUI has a new contentTransition type called symbolEffect, which can be used with Replace. So if I put the Image in a Button that toggles which symbol is displayed, the change is now animated. In AppKit and UIKit, you can use the new setSymbolImage method to change the image using a symbol content transition.
Finally, we have Appear and Disappear, which can show and hide symbols with unique animations. These effects are uniquely classified as transition effects. But before we get into that, we need to talk about parallel universes. Don't worry, though. It's not as complicated as it seems. In one universe, the image disappears, but the image view is still in the hierarchy. In other words, there's no change to the layout. The square and circle remain the same distance to each other. In the parallel universe, the image view is truly added and removed from the hierarchy. As a result, the layout of surrounding views may change.
The great news is that Appear and Disappear support both behaviors.
The first behavior is possible because Appear and Disappear are indefinite effects.
You know how to use indefinite effects already. In SwiftUI, use the .symbolEffect modifier and pass in .disappear. As the value of isMoonHidden updates, the Disappear effect is applied.
In AppKit and UIKit, use addSymbolEffect and pass in .disappear or .appear.
The takeaway here is that indefinite effects don't change the layout at all. They only alter the rendering of the symbol within the image view.
So that covers the first behavior. How do I jump to the parallel universe, where the surrounding layout changes? This is where the transition behavior comes in. Transition effects can be used with SwiftUI's built-in transition modifier, which animates a view's insertion or removal from the view hierarchy.
Let's convert the previous code to use the transition behavior. Instead of conditionally applying a Disappear effect, I'll instead conditionally add the symbol to the view hierarchy.
Then, I'll add a transition modifier. SwiftUI has a new transition type called– you guessed it–symbolEffect. By passing in .disappear, the symbol is now added and removed with animation.
You can also use a unique transition effect called Automatic. This effect will automatically perform the most appropriate transition animation for this symbol.
If you're not using SwiftUI, then you'll need to manually add and remove the image view from the hierarchy. UIKit features a completion handler for effects which can help. Simply add a Disappear effect, and when the effect finishes, remove the image view from the hierarchy. So there you have it. That's symbol effects in SwiftUI, AppKit, and UIKit. Now that you know the basics, here are some tips to take symbol effects to the next level in your apps.
First of all, the new UIKit methods on UIImageView are also available on UIBarButtonItem. This makes it easy to bring your toolbars to life using symbol animations. Some UIKit controls also have built-in symbol animations on iOS 17.
UISlider, for example, now bounces its images as the thumb reaches the ends of the track. You can control whether these animations play with the new isSymbolAnimationEnabled property on UIControl and UIBarButtonItem.
In SwiftUI, there are also some special considerations for disabling symbol effects.
Like other modifiers in SwiftUI, the symbolEffect modifier propagates through the view hierarchy. This means effects can be applied to multiple images by adding the modifier on a parent view. Use the symbolEffectsRemoved modifier to prevent a view from inheriting symbol effects. Now, some symbol effects, like Appear, Disappear, and Scale, change the symbol's appearance with animation. You may be interested in having a symbol be initially scaled up, or initially disappeared, without animation. In SwiftUI, you can do this using a transaction with animations disabled. Here, I use it to apply a scale.up effect without animation.
In AppKit and UIKit, use the animated parameter on addSymbolEffect to apply the effect without animation.
Finally, let's talk about variable value.
iOS 16 and macOS Ventura introduced variable value as another dimension for symbols, representing concepts like volume levels and signal strengths.
In iOS 17 and macOS Sonoma, we are making it super easy to crossfade between arbitrary variable values.
In SwiftUI, you don't need to do anything at all. Here, I have a Wi-Fi symbol whose variable value is based on some state– in this case, the current signal strength. As the signal strength changes, the Wi-Fi symbol automatically updates, while also animating across variable values. In AppKit and UIKit, use the automatic symbol content transition. It detects if the new symbol image just has a different variable value, and, if so, crossfades to the new value.
Thanks so much for joining me today. There's a lot of ways to animate symbols, so use the SF Symbols app to discover what's possible. Explore the Symbols framework, and try the new symbol effect APIs in SwiftUI, AppKit, and UIKit. And finally, adopt the animations to make your app's interface more delightful than ever.
Check out the other symbols sessions, too, for Human Interface guidelines on symbol animation, as well as updating custom symbols to support all the effects. Thanks, and happy coding.
-
-
6:02 - Symbol effects in SwiftUI
// Symbol effects in SwiftUI Image(systemName: "wifi.router") .symbolEffect(.variableColor.iterative.reversing) .symbolEffect(.scale.up)
-
6:02 - Symbol effects in AppKit and UIKit
let imageView: NSImageView = ... imageView.addSymbolEffect(.variableColor.iterative.reversing) imageView.addSymbolEffect(.scale.up)
-
6:49 - Indefinite symbol effects in SwiftUI
struct ContentView: View { @State var isConnectingToInternet: Bool = true var body: some View { Image(systemName: "wifi.router") .symbolEffect( .variableColor.iterative.reversing, isActive: isConnectingToInternet ) } }
-
7:09 - Indefinite symbol effects in AppKit and UIKit
let imageView: NSImageView = ... imageView.addSymbolEffect(.variableColor.iterative.reversing) // Later, remove the effect imageView.removeSymbolEffect(ofType: .variableColor)
-
8:26 - Discrete symbol effects in SwiftUI
struct ContentView: View { @State var bounceValue: Int = 0 var body: some View { VStack { Image(systemName: "antenna.radiowaves.left.and.right") .symbolEffect( .bounce, options: .repeat(2), value: bounceValue ) Button("Animate") { bounceValue += 1 } } } }
-
8:26 - Discrete symbol effects in AppKit and UIKit
let imageView: NSImageView = ... // Bounce imageView.addSymbolEffect(.bounce, options: .repeat(2))
-
9:40 - Content transition symbol effects in SwiftUI
struct ContentView: View { @State var isPaused: Bool = false var body: some View { Button { isPaused.toggle() } label: { Image(systemName: isPaused ? "pause.fill" : "play.fill") .contentTransition(.symbolEffect(.replace.offUp)) } } }
-
9:57 - Content transition symbol effects in AppKit and UIKit
let imageView: UIImageView = ... imageView.image = UIImage(systemName: "play.fill") // Change the image with a Replace effect let pauseImage = UIImage(systemName: "pause.fill")! imageView.setSymbolImage(pauseImage, contentTransition: .replace.offUp)
-
11:14 - Indefinite Appear and Disappear symbol effects in SwiftUI
struct ContentView: View { @State var isMoonHidden: Bool = false var body: some View { HStack { RoundedRectangle(cornerRadius: 5) Image(systemName: "moon.stars") .symbolEffect(.disappear, isActive: isMoonHidden) Circle() } } }
-
11:30 - Indefinite Appear and Disappear symbol effects in AppKit and UIKit
let imageView: UIImageView = ... imageView.image = UIImage(systemName: "moon.stars") imageView.addSymbolEffect(.disappear) // Re-appear the symbol imageView.addSymbolEffect(.appear)
-
12:38 - Transition symbol effects in SwiftUI
struct ContentView: View { @State var isMoonHidden: Bool = false var body: some View { HStack { RoundedRectangle(cornerRadius: 5) if !isMoonHidden { Image(systemName: "moon.stars") .transition(.symbolEffect(.disappear.down)) } Circle() } } }
-
12:59 - Appear and Disappear symbol effects in UIKit with completion handler
let imageView: UIImageView = ... imageView.image = UIImage(systemName: "moon.stars") imageView.addSymbolEffect(.disappear) { context in if let imageView = context.sender as? UIImageView, context.isFinished { imageView.removeFromSuperview() } }
-
14:19 - Symbol effect propagation in SwiftUI
VStack { Image(systemName: "figure.walk") .symbolEffectsRemoved() Image(systemName: "car") Image(systemName: "tram") } .symbolEffect(.pulse)
-
14:55 - Effects without animation in SwiftUI
struct ContentView: View { @State var isScaledUp: Bool = false var body: some View { Image(systemName: "iphone.radiowaves.left.and.right") .symbolEffect(.scale.up, isActive: isScaledUp) .onAppear { var transaction = Transaction() transaction.disablesAnimations = true withTransaction(transaction) { isScaledUp = true } } } }
-
15:06 - Effects without animation in AppKit and UIKit
// Effects without animation in AppKit and UIKit let imageView: UIImageView = ... imageView.image = UIImage(systemName: "iphone.radiowaves.left.and.right") imageView.addSymbolEffect(.disappear, animated: false)
-
15:44 - Variable value animations in SwiftUI
struct ContentView: View { @State var signalLevel: Double = 0.5 var body: some View { Image(systemName: "wifi", variableValue: signalLevel) } }
-
16:07 - Variable value animations in AppKit and UIKit
let imageView: UIImageView = ... imageView.image = UIImage(systemName: "wifi", variableValue: 1.0) // Animate to a different Wi-Fi level let currentSignalImage = UIImage( systemName: "wifi", variableValue: signalLevel )! imageView.setSymbolImage(currentSignalImage, contentTransition: .automatic)
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.