Symbol Effects: wbudowane słownictwo animacji SwiftUI dla każdej ikony
SF Symbols 5 (iOS 17) wprowadziło słownictwo animacji, którym może posługiwać się każda aplikacja iOS. SF Symbols 6 (iOS 18) je rozszerzyło. SF Symbols 7 (iOS 26) rozszerza je ponownie. Większość aplikacji wciąż renderuje swoje ikony jako statyczne obrazy. Słownictwo animacji znajduje się w SF Symbols.app i kryje się za pojedynczym modyfikatorem SwiftUI, nic nie kosztuje przy adopcji, zostało zaprojektowane przez zespół animacji Apple, aby sprawiało wrażenie natywnego, oraz respektuje ustawienia dostępności bez konieczności pracy w każdej aplikacji z osobna. Pominięcie tego jest jednym z najczęstszych wzorców utraty jakości w obecnie wydawanych aplikacjach iOS.
Słownictwo to nazywa zestaw rzeczy, które ikona może wykonać: może odbić się (bounce), pulsować (pulse), skalować się (scale), zastąpić siebie innym symbolem (replace), animować kolor pomiędzy swoimi warstwami (variable color), oddychać (breathe), obracać się (rotate), kołysać się (wiggle), pojawiać się (appear) lub znikać (disappear). Każdy czasownik ma określone znaczenie, określony charakter audiowizualny oraz określony moment w zachowaniu aplikacji, w którym zasługuje na swoje miejsce. System dostarcza programiście czasowniki; zadaniem programisty jest wybranie, który z nich pasuje do którego momentu.
TL;DR
- Modyfikator
.symbolEffect(...)w SwiftUI animuje dowolny SF Symbol z efektami obejmującymi.bounce,.pulse,.scale,.variableColor,.breathe,.rotate,.wiggle,.appearoraz.disappear1. - Osobna powierzchnia API,
.contentTransition(.symbolEffect(.replace)), uruchamia zaprojektowane przejście pomiędzy dwoma różnymi SF Symbols. Efektreplacenależy doContentTransition, a nie doSymbolEffect; oba współpracują, ale są różnymi API. - Efekty mogą uruchomić się raz (wyzwalane wartością przez
value:), być utrzymywane podczas gdy stan jest prawdą (wyzwalane stanem przezisActive:) lub powtarzać się w sposób ciągły (options: .repeating). - Wydajność jest praktycznie darmowa: animacje są renderowane przez zasób SF Symbol i wysyłane na GPU. Aplikacja ponosi koszt odczytu wartości w celu zdecydowania, kiedy uruchomić efekt.
- Dostępność jest obsłużona: efekty z dużą ilością ruchu respektują systemowe ustawienie Reduce Motion bez kodu w każdej aplikacji2.
Efekty według czasowników
Każdy efekt nazywa to, co ikona może zrobić. Należy wybrać czasownik na podstawie tego, co użytkownik powinien dostrzec w danym momencie.
.bounce
Pojedyncze elastyczne odbicie, konfigurowalne jako .up lub .down. Efekt sygnalizuje krótkie pozytywne zdarzenie: potwierdzenie, nadejście powiadomienia, zakończenie odświeżania. Jest to wizualny odpowiednik komunikatu „tak, to się stało”. Wyzwala się go parametrem value:: za każdym razem, gdy wartość się zmienia, symbol wykonuje jedno odbicie.
@State private var unreadCount = 0
Image(systemName: "bell.badge")
.symbolEffect(.bounce, value: unreadCount)
Wzorzec wyzwalany wartością uruchamia odbicie dokładnie raz na zmianę. Bez maszyny stanów, bez obliczeń czasu animacji, bez wpływu na układ.
.pulse
Powtarzające się pulsowanie nieprzezroczystości. Efekt sygnalizuje trwający stan, który chce zwrócić uwagę bez wywoływania niepokoju. Częste zastosowania: wskaźnik nadchodzącego połączenia, kropka oznaczająca trwające nagrywanie, plakietka „live”. Pulsowanie trwa nieprzerwanie, dopóki nie zostanie usunięte.
Image(systemName: "record.circle")
.symbolEffect(.pulse, options: .repeating)
.foregroundStyle(.red)
Opcja .repeating utrzymuje pulsowanie aktywne; brak .repeating uruchamia pojedyncze pulsowanie.
.scale
Zachowanie polegające na powiększaniu lub pomniejszaniu. Efekt podkreśla zmianę stanu odnoszącą się do wagi ikony: naciśnięcie przycisku, zaznaczenie elementu, ustawienie fokusu na kontrolce. Efekt scale obsługuje modyfikatory kierunku (.scale.up, .scale.down) oraz domyślny tryb dwukierunkowy; podczas utrzymywania symbol pozostaje przeskalowany.
Image(systemName: "heart")
.symbolEffect(.scale, isActive: isLiked)
Parametr isActive: to alternatywny wzorzec wyzwalania: dopóki wartość boolowska jest prawdą, efekt jest utrzymywany; gdy zmieni się na fałsz, efekt zostaje rozwiązany. Wzorzec ten pasuje do każdego stanu przełącznika, w którym animacja ikony powinna podążać bezpośrednio za stanem.
.variableColor
Animacja kolorów uwzględniająca poziomy. Efekt rozjaśnia warstwy symbolu po kolei (jak wypełniające się paski sygnału Wi-Fi czy ładująca się bateria). Opcje zachowania określają kierunek (.iterative przebiega do przodu, .cumulative wypełnia i utrzymuje, .reversing przebiega do przodu, a następnie do tyłu), a .dimInactiveLayers kontroluje, czy nieaktywne warstwy zanikają, czy znikają.
Image(systemName: "wifi")
.symbolEffect(
.variableColor.iterative.reversing.dimInactiveLayers,
options: .repeating
)
Efekt variable-color jest tym, czego używają iOS Control Center, Ustawienia oraz większość aplikacji Apple dla wszelkich symboli „siły sygnału” lub „wskaźnika poziomu”. Wzorzec jest rozpoznawalny w obrębie całej platformy, ponieważ animacja jest współdzielona w obrębie całej platformy.
.replace
Efekt, który łamie domyślne crossfade SwiftUI dla zamiany symboli. .contentTransition(.symbolEffect(.replace)) uruchamia zaprojektowane przejście pomiędzy dwoma symbolami, z opcją .downUp (odchodzący symbol przesuwa się w dół, nadchodzący w górę) lub domyślnym zachowaniem skalująco-zanikającym.
@State private var isPlaying = false
Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
.contentTransition(.symbolEffect(.replace))
.onTapGesture { isPlaying.toggle() }
Wzorzec ten jest właściwy dla każdego przycisku przełącznikowego (play/pause, mute/unmute, expand/collapse, like/unlike). Domyślne zachowanie SwiftUI wykonuje crossfade pomiędzy dwoma obrazami bez świadomości struktury symbolu; replace symbol-effect to zaprojektowane przejście, które respektuje strukturę symbolu.
.appear oraz .disappear
Warunkowe animacje pokazywania/ukrywania ikony pojawiającej się lub opuszczającej układ. Efekty te łączą się z przejściami widoków SwiftUI, sprawiając, że pojawienie się symbolu wydaje się celowe, a nie nagłe.
Image(systemName: "checkmark.circle.fill")
.symbolEffect(.appear, isActive: isVisible)
Warto ich używać, gdy obecność ikony sama w sobie ma znaczenie (wskaźnik potwierdzenia, plakietka statusu pojawiająca się po zakończeniu). Dla ikon stałych efekty te nie zasługują na swoje miejsce.
.breathe
Powolny ruch oddechowy łączący skalowanie i nieprzezroczystość (iOS 18+). Efekt sygnalizuje spokojny, ambientowy stan, który chce zwrócić wzrok użytkownika bez poczucia pilności. Liczniki medytacji, wskaźniki dźwięku ambientowego, stany bezczynności.
.rotate oraz .wiggle
Animacje obrotu i kołysania (iOS 18+). Rotate pasuje do stanów ładowania (odświeżająca się strzałka, synchronizujący się tryb). Wiggle pasuje do podpowiedzi „to można edytować, przeciągnij mnie” lub „coś wymaga twojej uwagi”. Oba mają opcje kierunku i prędkości.
Gramatyka: wyzwalacze i opcje
Każdy efekt obsługuje te same trzy wzorce wyzwalania. Należy wybrać ten, który pasuje do danego momentu.
Wyzwalany wartością (parametr value:). Efekt uruchamia się raz, gdy powiązana wartość się zmienia. Przydatny dla zdarzeń: licznik się zwiększa, stan przechodzi w inny. System odczytuje tożsamość wartości, uruchamia efekt i resetuje.
Wyzwalany stanem (parametr isActive:). Efekt działa, dopóki powiązana wartość boolowska jest prawdą. Przydatny dla utrzymywanych stanów: przełącznik, który powinien pulsować w trakcie aktywacji, wskaźnik nagrywania, który powinien pulsować podczas nagrywania.
Ciągły (options: .repeating). Efekt działa nieprzerwanie, dopóki modyfikator nie zostanie usunięty. Przydatny dla sygnałów ambientowych: wskaźnik ładowania, pulsująca plakietka „live”, oddychająca ikona medytacji.
Opcje każdego efektu doprecyzowują zachowanie: .speed(_) reguluje tempo animacji, .nonRepeating zastępuje domyślne zachowanie dla efektów, które wolą się powtarzać, modyfikatory kierunku (.up/.down/.iterative/.cumulative/.reversing) kształtują ruch. Każda opcja jest niewielka; kombinacje tworzą pełne słownictwo.
Sztuczka: warianty symboli między stanami
Bardziej subtelny wzorzec wykorzystuje efekty symboli z Image(systemName:) rozwiązywanymi względem nazw zależnych od stanu. Połączenie wyboru symbolu sterowanego stanem oraz .contentTransition(.symbolEffect(.replace)) pozwala pojedynczemu widokowi Image animować przejścia między wieloma stanami bez ręcznej pracy nad animacją.
@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)
Pięć stanów symboli, dwa nakładające się efekty (przejście replace pomiędzy symbolami, variable color w trakcie łączenia), jeden widok Image, brak niestandardowego kodu animacji. Wzorzec działa, ponieważ SF Symbols są zaprojektowane jako spójna rodzina; ta sama ikona połączenia w wielu siłach jest pojedynczą ideą wizualną wyrażoną w wielu rozdzielczościach.
Wydajność: dlaczego koszt jest praktycznie zerowy
Efekty symboli animują się na GPU, wysyłane przez tę samą ścieżkę renderowania, której SF Symbols już używają do statycznego renderowania. Animacje są zakodowane w samym zasobie symbolu; aplikacja odczytuje wartość, system planuje animację, GPU ją uruchamia. Nie ma pracy nad układem na klatkę, nie ma szarpania hierarchią widoków, nie ma kaskady objectWillChange.send().
Koszt, który ponosi programista, to koszt powiązania (binding) napędzającego wyzwalacz: właściwości @State, @Bindable, @Observable. Ten koszt istnieje niezależnie od animacji. Sama animacja jest praktycznie darmowym ulepszeniem statycznego renderowania.
Koszt ma znaczenie dla interfejsów z kamerą na żywo, komórek list z wieloma ikonami i każdej hierarchii widoków, w której 60 fps jest niepodlegające negocjacji. Efekty symboli można stosować swobodnie bez kosztu wydajnościowego niestandardowego bloku withAnimation; bazowy silnik wykonuje pracę.
Dostępność: Reduce Motion jest już respektowane
Efekty symboli automatycznie respektują systemowe ustawienie Reduce Motion. Efekty, które wiążą się ze znaczącym ruchem (.bounce, .scale, .rotate, .wiggle), zostają wytłumione lub pominięte, gdy Reduce Motion jest włączone. Efekty oparte głównie na nieprzezroczystości (.pulse, .breathe) zwykle pozostają, ponieważ nie wywołują problemów wrażliwości na ruch.
Zachowanie jest wbudowane w modyfikator SwiftUI; programista nie pisze if accessibilityReduceMotion { ... } else { ... } dla każdego efektu. Apple Human Interface Guidelines stwierdzają, że efekty honorują ustawienie systemowe, a implementacja SwiftUI jest zgodna z dokumentacją.
Dla programistów budujących aplikacje stawiające dostępność na pierwszym miejscu (aplikacje dla użytkowników z zaburzeniami przedsionkowymi, użytkowników słabowidzących, użytkowników wrażliwych na ruch) efekty symboli są właściwym wzorcem, ponieważ praca związana z dostępnością w każdej aplikacji wynosi zero.
Kiedy efekty symboli nie zasługują na swoje miejsce
Trzy tryby porażki warte nazwania.
Efekty na każdej ikonie cały czas. Widok pełen pulsujących, oddychających i odbijających się ikon jest trudniejszy do odczytania niż widok statyczny. Każdy efekt powinien sygnalizować konkretny moment; efekty wszędzie stają się szumem. Dyscyplina polega na zadaniu pytania „jaki moment ten efekt zaznacza?”, zanim się go doda. Jeśli odpowiedź brzmi „ikona istnieje”, warto wyciąć efekt.
Efekty walczące z treścią. Lista elementów, gdzie każdy ma kołyszącą się ikonę, nie mówi „edytuj mnie”; mówi „wszystko jest zepsute”. Efekt musi być zgodny z momentem w przepływie użytkownika. Wiggle to właściwy czasownik dla edytowalnej siatki w trybie edycji, a nie dla listy treści w stanie domyślnym.
Efekty na powierzchniach Liquid Glass bez testowania. Liquid Glass (omawiane w Wzorce SwiftUI dla Liquid Glass) załamuje to, co znajduje się za nim. Odbijająca się lub obracająca się ikona pod szkłem tworzy ruchome załamanie, które może konkurować z treścią poniżej. Należy przetestować to połączenie na rzeczywistym sprzęcie, zanim się je zatwierdzi.
Domyślna dyscyplina: każdy efekt jest opt-in, powiązany z konkretnym momentem znaczącym dla użytkownika oraz przetestowany pod kątem dostępności i wydajności. Słownictwo jest hojne; redaktorskie oko sprawia, że to działa.
Co nowego w SF Symbols 7 (iOS 26)
Coroczne wydanie SF Symbols od Apple zazwyczaj dodaje kilka tysięcy nowych symboli i udoskonala istniejące. Dla powierzchni API efektu symboli iOS 26, ostrożne podsumowanie:
Rozszerzone poziomy variable-color. Więcej istniejących symboli świadomych poziomów jest dostarczanych z bardziej drobnoziarnistą animacją variable-color, w tym symboli sieci i siły sygnału, które wcześniej skakały pomiędzy trzema poziomami, a teraz skaczą pomiędzy pięcioma.
Lepsza obsługa wiggle i rotate. Efekty ruchu dodane w iOS 18 otrzymały udoskonalenia poprawiające wydajność na słabszych urządzeniach oraz na visionOS, gdzie ruch w przestrzeni 3D wymaga innych wskazówek.
Przejścia replace dla symboli złożonych. Symbole zawierające wiele komponentów (heart-with-pulse, cloud-with-rain, person-with-clock) są zastępowane czyściej niż wcześniej, redukując wizualną szarpaninę przy przejściach pomiędzy powiązanymi symbolami złożonymi.
Główne możliwości (dziesięć efektów wymienionych powyżej) są dojrzałe od czasu SF Symbols 5 (iOS 17) i 6 (iOS 18). Dodatki w iOS 26 raczej rozszerzają niż wymyślają na nowo to słownictwo. Właściwym ruchem adopcyjnym jest dogłębne poznanie istniejących czasowników, a nie czekanie na kolejne wydanie.
Co ten wzorzec oznacza dla aplikacji iOS 26+
Trzy wnioski.
-
Czasowniki nie są opcjonalną dekoracją; są słownictwem, którym posługuje się platforma. Użytkownicy widzą to samo potwierdzenie
.bouncew każdej aplikacji Apple. Adopcja tych samych czasowników sprawia, że aplikacja zewnętrzna wydaje się natywna; wybór niestandardowych animacji, które nie pasują, sprawia, że aplikacja wydaje się nieprzystająca do platformy. Czasowniki są równocześnie zwycięstwem dla dostępności, wydajności i spójności platformy. -
Jeden efekt na moment. Widok, który używa
.bouncedla potwierdzenia,.replacedla przełączeń stanu i.variableColordla sygnałów na żywo, używa słownictwa właściwie. Widok, który pulsuje każdą ikoną w polu widzenia, używa go źle. Dyscyplina ma charakter redaktorski: który moment zasługuje na efekt? -
Należy zaufać domyślnym ustawieniom platformy w zakresie dostępności i wydajności. Efekty symboli automatycznie honorują Reduce Motion i działają na GPU przy niemal zerowym koszcie. Praca, którą programista musiałby w przeciwnym razie wykonać (pisanie warunków reduce-motion, dostrajanie czasu animacji do 60 fps), została już wykonana przez framework.
Pełny klaster Apple Ecosystem: typowane App Intents; serwery MCP; pytanie o routing; Foundation Models; rozróżnienie LLM runtime vs tooling; trzy powierzchnie; wzorzec pojedynczego źródła prawdy; Dwa serwery MCP; hooki dla rozwoju Apple; Live Activities; kontrakt runtime watchOS; wnętrzności SwiftUI; przestrzenny model mentalny RealityKit; dyscyplina schematu SwiftData; wzorce Liquid Glass; wieloplatformowe wydawanie; matryca platform; framework Vision; o czym odmawiam pisać. Hub znajduje się w Apple Ecosystem Series. W kontekście szerszym dotyczącym iOS z agentami AI proszę zajrzeć do iOS Agent Development guide.
FAQ
Jaka jest różnica pomiędzy .symbolEffect a .contentTransition(.symbolEffect(.replace))?
.symbolEffect(...) uruchamia animację na pojedynczym symbolu, który nie zmienia tożsamości (dzwonek nadal mówi „dzwonek”, ale się odbija). .contentTransition(.symbolEffect(.replace)) uruchamia zaprojektowane przejście pomiędzy dwoma różnymi symbolami (dzwonek staje się dzwonkiem-z-ukośnikiem). Pierwszy służy podkreśleniu stanu; drugi zamianie tożsamości symbolu.
Czy efekty symboli działają na visionOS?
Tak. Efekty symboli renderują się na visionOS w ten sam sposób, a systemowa adaptacja ruchu respektuje środowisko przestrzenne. Efekty wiggle i rotate na visionOS są dostrojone, aby wydawały się właściwe w odległości przestrzennej; programista nie pisze dla nich kodu specyficznego dla platformy.
Czy mogę pisać niestandardowe efekty symboli?
Zestaw efektów frameworka jest zamknięty; programista nie może zdefiniować nowego typu efektu. Zestaw jest na tyle hojny, że niestandardowe efekty są rzadko potrzebne. Dla animacji wykraczających poza słownictwo symboli właściwym narzędziem są natywne prymitywy animacji SwiftUI (.animation, withAnimation, Transaction, niestandardowe widoki Animatable), ale koszt na klatkę pozostaje po stronie programisty.
Czy używanie efektów symboli wpływa na recenzję w App Store?
Nie. Efekty symboli są publiczną powierzchnią API SwiftUI i są recenzowane identycznie jak każde inne użycie SwiftUI. Human Interface Guidelines aktywnie zachęcają do ich stosowania; aplikacja, która podąża za wytycznymi, ma mniej niespodzianek w recenzji niż taka, która buduje niestandardowe systemy animacji w tym samym celu.
Dlaczego mój efekt .bounce nie uruchamia się, gdy zmieniam wartość?
Trzy częste przyczyny. Po pierwsze, wartość musi rzeczywiście zmienić tożsamość (@State Int z 0 na 1, a nie ten sam Int 0 ponownie przypisany). Po drugie, kolejność modyfikatorów ma znaczenie: .symbolEffect(.bounce, value: foo) musi być zastosowany do Image, a nie do otaczającego Button ani HStack. Po trzecie, efekt uruchamia się raz na zmianę tożsamości; szybkie zmiany się zlewają. Dla efektów powtarzających się lub utrzymywanych warto użyć .repeating lub isActive: zamiast value:.
Bibliografia
-
Dokumentacja Apple Developer:
SymbolEffectoraz.symbolEffect(_:options:value:)w SwiftUI. ↩ -
Apple Human Interface Guidelines: Motion. Systemowe ustawienia ruchu (Reduce Motion) są automatycznie honorowane przez efekty SF Symbol. ↩