Symbol Effects : le vocabulaire d'animation intégré de SwiftUI pour chaque icône
SF Symbols 5 (iOS 17) a livré un vocabulaire d’animation que toute application iOS peut parler. SF Symbols 6 (iOS 18) l’a étendu. SF Symbols 7 (iOS 26) l’étend à nouveau. La plupart des applications affichent toujours leurs icônes comme des images statiques. Le vocabulaire d’animation se trouve à l’intérieur de SF Symbols.app et derrière un seul modificateur SwiftUI, ne coûtant rien à adopter, conçu par l’équipe d’animation d’Apple pour sembler natif et respectant les paramètres d’accessibilité sans travail propre à chaque application. Cette omission est l’un des schémas de perte de qualité les plus courants dans les applications iOS livrées en ce moment.
Le vocabulaire nomme un ensemble de choses qu’une icône peut faire : elle peut rebondir, pulser, changer d’échelle, se remplacer par un autre symbole, animer la couleur à travers ses calques, respirer, tourner, frétiller, apparaître ou disparaître. Chaque verbe a un sens spécifique, un caractère audiovisuel spécifique et un moment spécifique dans le comportement de l’application où il gagne sa place. Le système donne au développeur les verbes ; le travail du développeur est de choisir lequel convient à quel moment.
TL;DR
- Le modificateur
.symbolEffect(...)de SwiftUI anime n’importe quel SF Symbol avec des effets incluant.bounce,.pulse,.scale,.variableColor,.breathe,.rotate,.wiggle,.appearet.disappear1. - Une surface API distincte,
.contentTransition(.symbolEffect(.replace)), exécute une transition conçue entre deux SF Symbols différents. L’effetreplaceréside surContentTransition, pas surSymbolEffect; les deux coopèrent mais ce sont des APIs différents. - Les effets peuvent s’exécuter une fois (déclenchés par valeur via
value:), être maintenus pendant qu’un état est vrai (déclenchés par état viaisActive:) ou se répéter en continu (options: .repeating). - Les performances sont essentiellement gratuites : les animations sont rendues via l’asset SF Symbol et dispatchées sur le GPU. L’application paie le coût de la lecture d’une valeur pour décider quand déclencher.
- L’accessibilité est gérée : les effets à fort mouvement respectent le paramètre Réduire les animations du système sans code propre à chaque application2.
Les effets, par verbe
Chaque effet nomme ce qu’une icône peut faire. Choisissez le verbe selon ce que l’utilisateur doit percevoir à ce moment-là.
.bounce
Un seul rebond élastique, configurable en .up ou .down. L’effet signale un bref événement positif : une confirmation, l’arrivée d’une notification, un rafraîchissement qui se termine. C’est l’équivalent visuel de « oui, ça s’est produit ». Déclenchez-le avec un paramètre value: : chaque fois que la valeur change, le symbole rebondit une fois.
@State private var unreadCount = 0
Image(systemName: "bell.badge")
.symbolEffect(.bounce, value: unreadCount)
Le schéma déclenché par valeur exécute le rebond exactement une fois par changement. Pas de machine à états, pas de calculs de timing d’animation, pas d’impact sur la mise en page.
.pulse
Une pulsation d’opacité répétitive. L’effet signale une condition continue qui veut attirer l’attention sans devenir alarmante. Usages courants : un indicateur d’appel entrant, un point d’enregistrement en cours, un badge « live ». La pulsation s’exécute en continu jusqu’à ce qu’elle soit retirée.
Image(systemName: "record.circle")
.symbolEffect(.pulse, options: .repeating)
.foregroundStyle(.red)
L’option .repeating maintient la pulsation en vie ; l’absence de .repeating exécute une seule pulsation.
.scale
Un traitement d’agrandissement ou de réduction. L’effet souligne un changement d’état dans l’importance de l’icône : un bouton est pressé, un élément est sélectionné, un contrôle prend le focus. L’effet scale prend en charge des modificateurs de direction (.scale.up, .scale.down) et un comportement bidirectionnel par défaut ; tant qu’il est maintenu, le symbole reste agrandi.
Image(systemName: "heart")
.symbolEffect(.scale, isActive: isLiked)
Le paramètre isActive: est le schéma de déclenchement alternatif : tant que le booléen est vrai, l’effet est maintenu ; lorsqu’il bascule à faux, l’effet se résout. Le schéma convient à tout état à bascule où l’animation de l’icône doit suivre directement l’état.
.variableColor
Animation de couleur sensible aux paliers. L’effet allume les calques du symbole en séquence (pensez aux barres de signal Wi-Fi qui se remplissent, ou à une batterie en charge). Les options de comportement déterminent la direction (.iterative s’exécute vers l’avant, .cumulative remplit et maintient, .reversing s’exécute vers l’avant puis vers l’arrière), et .dimInactiveLayers contrôle si les calques hors-palier s’estompent ou disparaissent.
Image(systemName: "wifi")
.symbolEffect(
.variableColor.iterative.reversing.dimInactiveLayers,
options: .repeating
)
L’effet variable-color est ce que le Centre de contrôle iOS, les Réglages et la plupart des applications Apple utilisent pour tout symbole de « force du signal » ou « indicateur de niveau ». Le schéma est reconnaissable à travers la plateforme parce que l’animation est partagée à travers la plateforme.
.replace
L’effet qui rompt avec le fondu enchaîné par défaut de SwiftUI pour les permutations de symboles. .contentTransition(.symbolEffect(.replace)) exécute une transition conçue entre deux symboles, avec l’option de .downUp (le symbole sortant descend, le symbole entrant monte) ou le comportement par défaut de mise à l’échelle et de fondu.
@State private var isPlaying = false
Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
.contentTransition(.symbolEffect(.replace))
.onTapGesture { isPlaying.toggle() }
Le schéma est le bon pour tout bouton à bascule (lecture/pause, muet/non-muet, déplier/replier, j’aime/je n’aime plus). Le comportement par défaut de SwiftUI fait un fondu enchaîné entre les deux images sans aucune sympathie consciente du symbole ; le replace de symbol-effect est une transition conçue qui respecte la structure du symbole.
.appear et .disappear
Animations conditionnelles d’affichage/masquage pour une icône entrant ou quittant la mise en page. Les effets s’associent aux transitions de vues SwiftUI pour que l’apparition du symbole semble intentionnelle plutôt qu’abrupte.
Image(systemName: "checkmark.circle.fill")
.symbolEffect(.appear, isActive: isVisible)
Utilisez-les lorsque la présence d’une icône est elle-même significative (un indicateur de confirmation, un badge de statut qui apparaît à l’achèvement). Pour les icônes permanentes, les effets ne gagnent pas leur place.
.breathe
Un mouvement lent de respiration mêlant échelle et opacité (iOS 18+). L’effet signale un état calme et ambiant qui veut attirer le regard de l’utilisateur sans urgence. Minuteurs de méditation, indicateurs audio ambiants, états inactifs.
.rotate et .wiggle
Animations de rotation et de frétillement (iOS 18+). Rotate convient aux états de chargement (une flèche de rafraîchissement, un engrenage de synchronisation). Wiggle convient aux invites « ceci est modifiable, glissez-moi » ou « quelque chose nécessite votre attention ». Tous deux ont des options de direction et de vitesse.
La grammaire : déclencheurs et options
Chaque effet prend en charge les trois mêmes schémas de déclenchement. Choisissez celui qui correspond au moment.
Déclenché par valeur (paramètre value:). L’effet s’exécute une fois lorsque la valeur liée change. Utile pour les événements : un compteur qui s’incrémente, un état qui transite. Le système lit l’identité de la valeur, exécute l’effet et se réinitialise.
Déclenché par état (paramètre isActive:). L’effet s’exécute tant que le booléen lié est vrai. Utile pour les états maintenus : une bascule qui doit pulser pendant qu’elle est engagée, un indicateur d’enregistrement qui doit pulser pendant l’enregistrement.
Continu (options: .repeating). L’effet s’exécute en continu jusqu’à ce que le modificateur soit retiré. Utile pour les signaux ambiants : un indicateur de chargement, un badge live qui pulse, une icône de méditation qui respire.
Les options sur chaque effet affinent le comportement : .speed(_) ajuste le rythme de l’animation, .nonRepeating outrepasse la valeur par défaut pour les effets qui préfèrent se répéter, les modificateurs de direction (.up/.down/.iterative/.cumulative/.reversing) façonnent le mouvement. Chaque option est petite ; les combinaisons produisent un vocabulaire complet.
L’astuce : variantes de symboles à travers les états
Un schéma plus subtil utilise les symbol effects avec Image(systemName:) résolu contre des noms pilotés par l’état. La combinaison de la sélection de symbole pilotée par l’état et de .contentTransition(.symbolEffect(.replace)) permet à une seule vue Image d’animer entre de nombreux états sans aucun travail d’animation manuel.
@State private var connectionState: ConnectionState = .disconnected
var symbol: String {
switch connectionState {
case .disconnected: "wifi.slash"
case .connecting: "wifi.exclamationmark"
case .weak: "wifi.low"
case .medium: "wifi.medium"
case .strong: "wifi"
}
}
Image(systemName: symbol)
.contentTransition(.symbolEffect(.replace))
.symbolEffect(.variableColor.iterative, options: .repeating, value: connectionState == .connecting)
Cinq états de symbole, deux effets superposés (transition replace entre symboles, variable color pendant la connexion), une vue Image, aucun code d’animation personnalisé. Le schéma fonctionne parce que les SF Symbols sont conçus comme une famille cohérente ; la même icône de connexion à plusieurs intensités est une seule idée visuelle exprimée à plusieurs résolutions.
Performances : pourquoi le coût est effectivement nul
Les symbol effects s’animent sur le GPU, dispatchés via le même chemin de rendu que SF Symbols utilise déjà pour le rendu statique. Les animations sont encodées dans l’asset du symbole lui-même ; l’application lit une valeur, le système planifie l’animation, le GPU l’exécute. Il n’y a pas de travail de mise en page par image, pas de remue-ménage dans la hiérarchie des vues, pas de cascade objectWillChange.send().
Le coût que paie le développeur est celui du binding qui pilote le déclencheur : la propriété @State, @Bindable ou @Observable. Ce coût existe indépendamment de l’animation. L’animation elle-même est essentiellement une amélioration gratuite par rapport à un rendu statique.
Le coût compte pour les UI de caméra en direct, les cellules de liste avec de nombreuses icônes et toute hiérarchie de vues où le 60 fps n’est pas négociable. Les symbol effects peuvent être appliqués libéralement sans le coût en performances d’un bloc withAnimation personnalisé ; le moteur sous-jacent gère le travail.
Accessibilité : Réduire les animations est déjà respecté
Les symbol effects respectent automatiquement le paramètre Réduire les animations du système. Les effets impliquant un mouvement significatif (.bounce, .scale, .rotate, .wiggle) s’atténuent ou sont sautés lorsque Réduire les animations est activé. Les effets qui sont principalement basés sur l’opacité (.pulse, .breathe) tendent à demeurer parce qu’ils ne déclenchent pas de problèmes de sensibilité au mouvement.
Le comportement est intégré dans le modificateur SwiftUI ; le développeur n’écrit pas if accessibilityReduceMotion { ... } else { ... } pour chaque effet. Les Apple Human Interface Guidelines indiquent que les effets honorent le paramètre système, et l’implémentation SwiftUI correspond à la documentation.
Pour les développeurs qui construisent des applications axées sur l’accessibilité (applications pour utilisateurs atteints de troubles vestibulaires, utilisateurs malvoyants, utilisateurs sensibles au mouvement), les symbol effects sont le bon schéma car le travail d’accessibilité propre à chaque application est nul.
Quand les symbol effects ne gagnent pas leur place
Trois modes d’échec qui méritent d’être nommés.
Effets sur chaque icône en permanence. Une vue remplie d’icônes qui pulsent, respirent et rebondissent est plus difficile à lire qu’une vue statique. Chaque effet doit signaler un moment spécifique ; des effets partout deviennent du bruit. La discipline est de se demander « quel moment cet effet marque-t-il ? » avant de l’ajouter. Si la réponse est « l’icône existe », coupez l’effet.
Effets qui combattent le contenu. Une liste d’éléments dont chaque icône frétille ne dit pas « modifie-moi » ; elle dit « tout est cassé ». L’effet doit s’aligner avec le moment dans le flux utilisateur. Wiggle est le bon verbe pour une grille modifiable en mode édition, pas pour une liste de contenus dans son état par défaut.
Effets sur des surfaces Liquid Glass sans test. Liquid Glass (couvert dans Liquid Glass SwiftUI Patterns) réfracte ce qui se trouve derrière lui. Une icône qui rebondit ou tourne sous le verre produit une réfraction mouvante qui peut entrer en concurrence avec le contenu sous-jacent. Testez la combinaison sur du matériel réel avant de vous engager.
La discipline par défaut : chaque effet est opt-in, lié à un moment spécifique significatif pour l’utilisateur, et testé pour l’accessibilité et les performances. Le vocabulaire est généreux ; c’est l’œil de l’éditeur qui le fait fonctionner.
Quoi de neuf dans SF Symbols 7 (iOS 26)
La sortie annuelle de SF Symbols par Apple ajoute généralement plusieurs milliers de nouveaux symboles et raffine ceux qui existent. Pour les APIs symbol-effect d’iOS 26, le résumé prudent :
Paliers de variable-color étendus. Davantage de symboles existants sensibles aux paliers livrent une animation variable-color à grain plus fin, y compris les symboles de réseau et de force du signal qui passaient auparavant par trois paliers et passent désormais par cinq.
Meilleure gestion de wiggle et rotate. Les effets de mouvement ajoutés dans iOS 18 ont reçu des raffinements qui améliorent les performances sur les appareils d’entrée de gamme et sur visionOS, où le mouvement dans l’espace 3D nécessite des indices différents.
Transitions replace de symboles pour les symboles composés. Les symboles à plusieurs composants (heart-with-pulse, cloud-with-rain, person-with-clock) se remplacent plus proprement qu’auparavant, réduisant les saccades visuelles lors des transitions entre symboles composés liés.
Les capacités phares (les dix effets ci-dessus) sont matures depuis SF Symbols 5 (iOS 17) et 6 (iOS 18). Les ajouts d’iOS 26 étendent plutôt que réinventent le vocabulaire. Le bon mouvement d’adoption est d’apprendre profondément les verbes existants plutôt que d’attendre la prochaine version.
Ce que ce schéma signifie pour les applications iOS 26+
Trois enseignements.
-
Les verbes ne sont pas une décoration optionnelle ; ce sont un vocabulaire que la plateforme parle. Les utilisateurs voient la même confirmation
.bouncedans toutes les applications Apple. Adopter les mêmes verbes fait sembler une application tierce native ; choisir des animations personnalisées qui ne correspondent pas fait sembler l’application hors-plateforme. Les verbes sont une victoire à la fois pour l’accessibilité, les performances et la cohérence de la plateforme. -
Un effet par moment. Une vue qui utilise
.bouncepour la confirmation,.replacepour les bascules d’état et.variableColorpour les signaux en direct utilise correctement le vocabulaire. Une vue qui fait pulser chaque icône en vue l’utilise mal. La discipline est éditoriale : quel moment mérite l’effet ? -
Faites confiance aux valeurs par défaut de la plateforme en matière d’accessibilité et de performances. Les symbol effects honorent automatiquement Réduire les animations et s’exécutent sur le GPU à un coût quasi nul. Le travail que le développeur ferait autrement (écrire des conditionnels reduce-motion, régler le timing d’animation pour 60 fps) est déjà fait par le framework.
La grappe complète de l’écosystème Apple : App Intents typés ; serveurs MCP ; la question du routage ; Foundation Models ; la distinction LLM runtime vs outillage ; trois surfaces ; le schéma de source unique de vérité ; Deux serveurs MCP ; hooks pour le développement Apple ; Live Activities ; le runtime watchOS ; les internes de SwiftUI ; le modèle mental spatial de RealityKit ; la discipline de schéma SwiftData ; les schémas Liquid Glass ; livraison multi-plateforme ; la matrice des plateformes ; Vision framework ; ce que je refuse d’écrire. Le hub se trouve dans la série Apple Ecosystem. Pour un contexte plus large iOS-avec-agents-IA, consultez le guide de développement d’agents iOS.
FAQ
Quelle est la différence entre .symbolEffect et .contentTransition(.symbolEffect(.replace)) ?
.symbolEffect(...) exécute une animation sur un seul symbole qui ne change pas d’identité (la cloche reste « cloche » mais rebondit). .contentTransition(.symbolEffect(.replace)) exécute une transition conçue entre deux symboles différents (la cloche devient une cloche barrée). Le premier sert à mettre l’accent sur un état ; le second sert à échanger l’identité du symbole.
Les symbol effects fonctionnent-ils sur visionOS ?
Oui. Les symbol effects s’affichent de la même manière sur visionOS, l’adaptation du mouvement par le système respectant l’environnement spatial. Les effets wiggle et rotate sur visionOS sont réglés pour sembler justes à distance spatiale ; le développeur n’écrit pas de code spécifique à la plateforme pour eux.
Puis-je écrire des symbol effects personnalisés ?
L’ensemble d’effets du framework est fermé ; le développeur ne peut pas définir un nouveau type d’effet. L’ensemble est suffisamment généreux pour que des effets personnalisés soient rarement nécessaires. Pour des animations au-delà du vocabulaire des symboles, les primitives d’animation natives de SwiftUI (.animation, withAnimation, Transaction, vues Animatable personnalisées) sont le bon outil, mais le coût par image est à la charge du développeur.
L’utilisation des symbol effects affecte-t-elle l’examen App Store ?
Non. Les symbol effects sont une API SwiftUI publique et sont examinés à l’identique de tout autre usage SwiftUI. Les Human Interface Guidelines encouragent activement leur usage ; une application qui suit les guidelines a moins de surprises à l’examen qu’une qui construit des systèmes d’animation personnalisés dans le même but.
Pourquoi mon effet .bounce ne s’exécute-t-il pas lorsque je change la valeur ?
Trois causes courantes. Premièrement, la valeur doit réellement changer d’identité (@State Int de 0 à 1, et non le même Int 0 réassigné). Deuxièmement, l’ordre des modificateurs compte : .symbolEffect(.bounce, value: foo) doit s’appliquer à l’Image, pas à un Button ou HStack qui l’enveloppe. Troisièmement, l’effet s’exécute une fois par changement d’identité ; les changements rapides se fondent. Pour des effets répétitifs ou maintenus, utilisez .repeating ou isActive: au lieu de value:.
Références
-
Documentation développeur Apple :
SymbolEffectet.symbolEffect(_:options:value:)dans SwiftUI. ↩ -
Apple Human Interface Guidelines : Motion. Les paramètres de mouvement du système (Réduire les animations) sont honorés automatiquement par les effets SF Symbol. ↩