{"id":"1c4aa5d6-c8d1-4752-8e09-62039fd856d4","shortId":"xKPemS","kind":"skill","title":"swiftui-animation","tagline":"Implement, review, or improve SwiftUI animations and transitions. Use when adding explicit animations with withAnimation, configuring implicit animations with .animation(_:body:) or .animation(_:value:), configuring spring animations (.smooth, .snappy, .bouncy), building phase or","description":"# SwiftUI Animation (iOS 26+)\n\nReview, write, and fix SwiftUI animations. Apply modern animation APIs with\ncorrect timing, transitions, and accessibility handling using Swift 6.3 patterns.\n\n## Contents\n\n- [Triage Workflow](#triage-workflow)\n- [withAnimation (Explicit Animation)](#withanimation-explicit-animation)\n- [Implicit Animation](#implicit-animation)\n- [Spring Type (iOS 17+)](#spring-type-ios-17)\n- [PhaseAnimator (iOS 17+)](#phaseanimator-ios-17)\n- [KeyframeAnimator (iOS 17+)](#keyframeanimator-ios-17)\n- [@Animatable Macro](#animatable-macro)\n- [matchedGeometryEffect (iOS 14+)](#matchedgeometryeffect-ios-14)\n- [Navigation Zoom Transition (iOS 18+)](#navigation-zoom-transition-ios-18)\n- [Transitions (iOS 17+)](#transitions-ios-17)\n- [ContentTransition (iOS 16+)](#contenttransition-ios-16)\n- [Symbol Effects (iOS 17+)](#symbol-effects-ios-17)\n- [Symbol Rendering Modes](#symbol-rendering-modes)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n## Triage Workflow\n\n### Step 1: Identify the animation category\n\n| Category | API | When to use |\n|---|---|---|\n| State-driven | `withAnimation`, `.animation(_:body:)`, `.animation(_:value:)` | Explicit state changes, selective modifier animation, or simple value-bound changes |\n| Multi-phase | `PhaseAnimator` | Sequenced multi-step animations |\n| Keyframe | `KeyframeAnimator` | Complex multi-property choreography |\n| Shared element | `matchedGeometryEffect` | Layout-driven hero transitions |\n| Navigation | `matchedTransitionSource` + `.navigationTransition(.zoom)` | NavigationStack push/pop zoom |\n| View lifecycle | `.transition()` | Insertion and removal |\n| Text content | `.contentTransition()` | In-place text/number changes |\n| Symbol | `.symbolEffect()` | SF Symbol animations |\n| Custom | `CustomAnimation` protocol | Novel timing curves |\n\n### Step 2: Choose the animation curve\n\n```swift\n// Timing curves\n.linear                              // constant speed\n.easeIn(duration: 0.3)              // slow start\n.easeOut(duration: 0.3)             // slow end\n.easeInOut(duration: 0.3)           // slow start and end\n\n// Spring presets (preferred for natural motion)\n.smooth                              // no bounce, fluid\n.smooth(duration: 0.5, extraBounce: 0.0)\n.snappy                              // small bounce, responsive\n.snappy(duration: 0.4, extraBounce: 0.1)\n.bouncy                              // visible bounce, playful\n.bouncy(duration: 0.5, extraBounce: 0.2)\n\n// Custom spring\n.spring(duration: 0.5, bounce: 0.3, blendDuration: 0.0)\n.spring(Spring(duration: 0.6, bounce: 0.2), blendDuration: 0.0)\n.interactiveSpring(response: 0.15, dampingFraction: 0.86)\n```\n\n### Step 3: Apply and verify\n\n- Confirm animation triggers on the correct state change.\n- Test with Accessibility > Reduce Motion enabled.\n- Verify no expensive work runs inside animation content closures.\n\n## withAnimation (Explicit Animation)\n\n```swift\nwithAnimation(.spring) { isExpanded.toggle() }\n\n// With completion (iOS 17+)\nwithAnimation(.smooth(duration: 0.35), completionCriteria: .logicallyComplete) {\n    isExpanded = true\n} completion: { loadContent() }\n```\n\n## Implicit Animation\n\nPrefer `.animation(_:body:)` when only specific modifiers should animate.\nUse `.animation(_:value:)` for simple value-bound changes that can animate the\nview's animatable modifiers together.\n\n```swift\nBadge()\n    .foregroundStyle(isActive ? .green : .secondary)\n    .animation(.snappy) { content in\n        content\n            .scaleEffect(isActive ? 1.15 : 1.0)\n            .opacity(isActive ? 1.0 : 0.7)\n    }\n```\n\n```swift\nCircle()\n    .scaleEffect(isActive ? 1.2 : 1.0)\n    .opacity(isActive ? 1.0 : 0.6)\n    .animation(.bouncy, value: isActive)\n```\n\n## Spring Type (iOS 17+)\n\nFour initializer forms for different mental models.\n\n```swift\n// Perceptual (preferred)\nSpring(duration: 0.5, bounce: 0.3)\n\n// Physical\nSpring(mass: 1.0, stiffness: 100.0, damping: 10.0)\n\n// Response-based\nSpring(response: 0.5, dampingRatio: 0.7)\n\n// Settling-based\nSpring(settlingDuration: 1.0, dampingRatio: 0.8)\n```\n\nThree presets mirror Animation presets: `.smooth`, `.snappy`, `.bouncy`.\n\n## PhaseAnimator (iOS 17+)\n\nCycle through discrete phases with per-phase animation curves.\n\n```swift\nenum PulsePhase: CaseIterable {\n    case idle, grow, shrink\n}\n\nstruct PulsingDot: View {\n    var body: some View {\n        PhaseAnimator(PulsePhase.allCases) { phase in\n            Circle()\n                .frame(width: 40, height: 40)\n                .scaleEffect(phase == .grow ? 1.4 : 1.0)\n                .opacity(phase == .shrink ? 0.5 : 1.0)\n        } animation: { phase in\n            switch phase {\n            case .idle: .easeIn(duration: 0.2)\n            case .grow: .spring(duration: 0.4, bounce: 0.3)\n            case .shrink: .easeOut(duration: 0.3)\n            }\n        }\n    }\n}\n```\n\nTrigger-based variant runs one cycle per trigger change:\n\n```swift\nPhaseAnimator(PulsePhase.allCases, trigger: tapCount) { phase in\n    // ...\n} animation: { _ in .spring(duration: 0.4) }\n```\n\n## KeyframeAnimator (iOS 17+)\n\nAnimate multiple properties along independent timelines.\n\n```swift\nstruct AnimValues {\n    var scale: Double = 1.0\n    var yOffset: Double = 0.0\n    var opacity: Double = 1.0\n}\n\nstruct BounceView: View {\n    @State private var trigger = false\n\n    var body: some View {\n        Button { trigger.toggle() } label: {\n            Image(systemName: \"star.fill\")\n                .font(.largeTitle)\n                .keyframeAnimator(\n                    initialValue: AnimValues(),\n                    trigger: trigger\n                ) { content, value in\n                    content\n                        .scaleEffect(value.scale)\n                        .offset(y: value.yOffset)\n                        .opacity(value.opacity)\n                } keyframes: { _ in\n                    KeyframeTrack(\\.scale) {\n                        SpringKeyframe(1.5, duration: 0.3)\n                        CubicKeyframe(1.0, duration: 0.4)\n                    }\n                    KeyframeTrack(\\.yOffset) {\n                        CubicKeyframe(-30, duration: 0.2)\n                        CubicKeyframe(0, duration: 0.4)\n                    }\n                    KeyframeTrack(\\.opacity) {\n                        LinearKeyframe(0.6, duration: 0.15)\n                        LinearKeyframe(1.0, duration: 0.25)\n                    }\n                }\n        }\n        .buttonStyle(.plain)\n    }\n}\n```\n\nKeyframe types: `LinearKeyframe` (linear), `CubicKeyframe` (smooth curve),\n`SpringKeyframe` (spring physics), `MoveKeyframe` (instant jump).\n\nUse `repeating: true` for looping keyframe animations.\n\n## @Animatable Macro\n\nReplaces manual `AnimatableData` boilerplate. Attach to any type with\nanimatable stored properties.\n\n```swift\n// Replaces manual AnimatableData boilerplate\n@Animatable\nstruct WaveShape: Shape {\n    var frequency: Double\n    var amplitude: Double\n    var phase: Double\n    @AnimatableIgnored var lineWidth: CGFloat\n\n    func path(in rect: CGRect) -> Path {\n        // draw wave using frequency, amplitude, phase\n    }\n}\n```\n\nRules:\n- Stored properties must conform to `VectorArithmetic`.\n- Use `@AnimatableIgnored` to exclude non-animatable properties.\n- Computed properties are never included.\n\n## matchedGeometryEffect (iOS 14+)\n\nSynchronize geometry between views for shared-element animations.\n\n```swift\nstruct HeroView: View {\n    @Namespace private var heroSpace\n    @State private var isExpanded = false\n\n    var body: some View {\n        Group {\n            if isExpanded {\n                Button {\n                    withAnimation(.spring(duration: 0.4, bounce: 0.2)) {\n                        isExpanded = false\n                    }\n                } label: {\n                    DetailCard()\n                        .matchedGeometryEffect(id: \"card\", in: heroSpace)\n                }\n            } else {\n                Button {\n                    withAnimation(.spring(duration: 0.4, bounce: 0.2)) {\n                        isExpanded = true\n                    }\n                } label: {\n                    ThumbnailCard()\n                        .matchedGeometryEffect(id: \"card\", in: heroSpace)\n                }\n            }\n        }\n        .buttonStyle(.plain)\n    }\n}\n```\n\nExactly one view per ID must be visible at a time for the interpolation to work.\n\n## Navigation Zoom Transition (iOS 18+)\n\nPair `matchedTransitionSource` on the source view with\n`.navigationTransition(.zoom(...))` on the destination.\n\n```swift\nstruct GalleryView: View {\n    @Namespace private var zoomSpace\n    let items: [GalleryItem]\n\n    var body: some View {\n        NavigationStack {\n            ScrollView {\n                LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {\n                    ForEach(items) { item in\n                        NavigationLink {\n                            GalleryDetail(item: item)\n                                .navigationTransition(\n                                    .zoom(sourceID: item.id, in: zoomSpace)\n                                )\n                        } label: {\n                            ItemThumbnail(item: item)\n                                .matchedTransitionSource(\n                                    id: item.id, in: zoomSpace\n                                )\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\nApply `.navigationTransition` on the destination view, not on inner containers.\n\n## Transitions (iOS 17+)\n\nControl how views animate on insertion and removal.\n\n```swift\nif showBanner {\n    BannerView()\n        .transition(.move(edge: .top).combined(with: .opacity))\n}\n```\n\nBuilt-in types: `.opacity`, `.slide`, `.scale`, `.scale(_:anchor:)`,\n`.move(edge:)`, `.push(from:)`, `.offset(x:y:)`, `.identity`,\n`.blurReplace`, `.blurReplace(_:)`, `.symbolEffect`,\n`.symbolEffect(_:options:)`.\n\nAsymmetric transitions:\n\n```swift\n.transition(.asymmetric(\n    insertion: .push(from: .bottom),\n    removal: .opacity\n))\n```\n\n## ContentTransition (iOS 16+)\n\nAnimate in-place content changes without insertion/removal.\n\n```swift\nText(\"\\(score)\")\n    .contentTransition(.numericText(countsDown: false))\n    .animation(.snappy, value: score)\n\n// For SF Symbols\nImage(systemName: isMuted ? \"speaker.slash\" : \"speaker.wave.3\")\n    .contentTransition(.symbolEffect(.replace.downUp))\n```\n\nTypes: `.identity`, `.interpolate`, `.opacity`,\n`.numericText(countsDown:)`, `.numericText(value:)`, `.symbolEffect`.\n\n## Symbol Effects (iOS 17+)\n\nAnimate SF Symbols with semantic effects.\n\n```swift\n// Discrete (triggers on value change)\nImage(systemName: \"bell.fill\")\n    .symbolEffect(.bounce, value: notificationCount)\n\nImage(systemName: \"arrow.clockwise\")\n    .symbolEffect(.wiggle.clockwise, value: refreshCount)\n\n// Indefinite (active while condition holds)\nImage(systemName: \"wifi\")\n    .symbolEffect(.pulse, isActive: isSearching)\n\nImage(systemName: \"mic.fill\")\n    .symbolEffect(.breathe, isActive: isRecording)\n\n// Variable color with chaining\nImage(systemName: \"speaker.wave.3.fill\")\n    .symbolEffect(\n        .variableColor.iterative.reversing.dimInactiveLayers,\n        options: .repeating,\n        isActive: isPlaying\n    )\n```\n\nAll effects: `.bounce`, `.pulse`, `.variableColor`, `.scale`, `.appear`,\n`.disappear`, `.replace`, `.breathe`, `.rotate`, `.wiggle`.\n\nScope: `.byLayer`, `.wholeSymbol`. Direction varies per effect.\n\n## Symbol Rendering Modes\n\nControl how SF Symbol layers are colored with `.symbolRenderingMode(_:)`.\n\n| Mode | Effect | When to use |\n|------|--------|-------------|\n| `.monochrome` | Single color applied uniformly (default) | Toolbars, simple icons matching text |\n| `.hierarchical` | Single color with opacity layers for depth | Subtle depth without multiple colors |\n| `.multicolor` | System-defined fixed colors per layer | Weather, file types — Apple's intended palette |\n| `.palette` | Custom colors per layer via `.foregroundStyle` | Brand colors, custom multi-color icons |\n\n```swift\n// Hierarchical — single tint, opacity layers for depth\nImage(systemName: \"speaker.wave.3.fill\")\n    .symbolRenderingMode(.hierarchical)\n    .foregroundStyle(.blue)\n\n// Palette — custom color per layer\nImage(systemName: \"person.crop.circle.badge.plus\")\n    .symbolRenderingMode(.palette)\n    .foregroundStyle(.blue, .green)\n\n// Multicolor — system-defined colors\nImage(systemName: \"cloud.sun.rain.fill\")\n    .symbolRenderingMode(.multicolor)\n```\n\n**Variable color:** `.symbolVariableColor(value:)` for percentage-based fill (signal strength, volume):\n\n```swift\nImage(systemName: \"wifi\")\n    .symbolVariableColor(value: signalStrength) // 0.0–1.0\n```\n\n> **Docs:** [SymbolRenderingMode](https://sosumi.ai/documentation/swiftui/symbolrenderingmode) · [symbolRenderingMode(_:)](https://sosumi.ai/documentation/swiftui/view/symbolrenderingmode(_:))\n\n## Common Mistakes\n\n### 1. Using bare `.animation(_:)` when you need precise scope\n\n```swift\n// TOO BROAD — applies when the view changes\n.animation(.easeIn)\n\n// CORRECT — bind animation to one value\n.animation(.easeIn, value: isVisible)\n\n// CORRECT — scope animation to selected modifiers\n.animation(.easeIn) { content in\n    content.opacity(isVisible ? 1.0 : 0.0)\n}\n```\n\n### 2. Expensive work inside animation closures\n\nNever run heavy computation in `keyframeAnimator` / `PhaseAnimator` content closures — they execute every frame. Precompute outside, animate only visual properties.\n\n### 3. Missing reduce motion support\n\n```swift\n@Environment(\\.accessibilityReduceMotion) private var reduceMotion\nwithAnimation(reduceMotion ? .none : .bouncy) { showDetail = true }\n```\n\n### 4. Multiple matchedGeometryEffect sources\n\nOnly one view per ID should be visible at a time. Two visible views with the same ID causes undefined layout.\n\n### 5. Using DispatchQueue or UIView.animate\n\n```swift\n// WRONG\nDispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { withAnimation { isVisible = true } }\n// CORRECT\nwithAnimation(.spring.delay(0.5)) { isVisible = true }\n```\n\n### 6. Forgetting animation on ContentTransition\n\n```swift\n// WRONG — no animation, content transition has no effect\nText(\"\\(count)\").contentTransition(.numericText(countsDown: true))\n// CORRECT — pair with animation\nText(\"\\(count)\")\n    .contentTransition(.numericText(countsDown: true))\n    .animation(.snappy, value: count)\n```\n\n### 7. navigationTransition on wrong view\n\nApply `.navigationTransition(.zoom(sourceID:in:))` on the outermost destination view, not inside a container.\n\n## Review Checklist\n\n- [ ] Animation curve matches intent (spring for natural, ease for mechanical)\n- [ ] `withAnimation` wraps the state change; implicit animation uses `.animation(_:body:)` for selective modifier scope or `.animation(_:value:)` with an explicit value\n- [ ] `matchedGeometryEffect` has exactly one source per ID; zoom uses matching `id`/`namespace`\n- [ ] `@Animatable` macro used instead of manual `animatableData`\n- [ ] `accessibilityReduceMotion` checked; no `DispatchQueue`/`UIView.animate`\n- [ ] Transitions use `.transition()`; `contentTransition` is paired with animation and uses the narrowest implicit animation scope that fits\n- [ ] Animated state changes on @MainActor; animation-driving types are Sendable\n\n## References\n\n- See [references/animation-advanced.md](references/animation-advanced.md) for CustomAnimation protocol, full Spring variants, all Transition types, symbol effect details, Transaction system, UnitCurve types, and performance guidance.\n- Core Animation bridging patterns: [references/core-animation-bridge.md](references/core-animation-bridge.md)","tags":["swiftui","animation","swift","ios","skills","dpearson2699","accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills"],"capabilities":["skill","source-dpearson2699","skill-swiftui-animation","topic-accessibility","topic-agent-skills","topic-ai-coding","topic-apple","topic-claude-code","topic-codex-skills","topic-cursor-skills","topic-ios","topic-ios-development","topic-liquid-glass","topic-localization","topic-mapkit"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-animation","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add dpearson2699/swift-ios-skills","source_repo":"https://github.com/dpearson2699/swift-ios-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 599 github stars · SKILL.md body (15,650 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:53:45.515Z","embedding":null,"createdAt":"2026-04-18T20:32:34.027Z","updatedAt":"2026-05-18T18:53:45.515Z","lastSeenAt":"2026-05-18T18:53:45.515Z","tsv":"'-30':670 '/documentation/swiftui/symbolrenderingmode)':1245 '/documentation/swiftui/view/symbolrenderingmode(_:))':1249 '0':674 '0.0':300,327,335,614,1239,1294 '0.1':309 '0.15':338,682 '0.2':318,333,560,672,815,832 '0.25':686 '0.3':271,276,281,325,470,567,572,662 '0.35':383 '0.4':307,565,594,666,676,813,830 '0.5':298,316,323,468,484,549,1372,1379 '0.6':331,447,680 '0.7':437,486 '0.8':494 '0.86':340 '1':171,1252 '1.0':433,436,443,446,474,492,545,550,610,618,664,684,1240,1293 '1.15':432 '1.2':442 '1.4':544 '1.5':660 '10.0':478 '100':899 '100.0':476 '14':110,114,779 '16':135,139,990 '17':83,88,91,95,98,102,128,132,143,148,379,455,505,597,935,1034 '18':119,125,864 '2':258,1295 '26':40 '3':342,1018,1320 '4':1337 '40':538,540 '5':1362 '6':1382 '6.3':60 '7':1416 'access':56,356 'accessibilityreducemot':1327,1487 'activ':1062 'ad':14 'adapt':897 'along':601 'amplitud':736,755 'anchor':963 'anim':3,9,16,21,23,26,30,38,46,49,70,74,76,79,174,185,187,194,209,250,261,347,366,371,391,393,400,402,412,425,448,498,514,551,590,598,708,788,939,991,1006,1035,1255,1269,1273,1277,1283,1287,1299,1316,1384,1390,1405,1412,1437,1453,1455,1462,1499,1505,1509,1515,1544 'animat':103,106,416,709,720,728,770,1480 'animatable-macro':105 'animatabledata':713,726,1486 'animatableignor':741,765 'animation-driv':1514 'animvalu':606,641 'api':50,177 'appear':1099 'appl':1164 'appli':47,343,923,1132,1264,1421 'arrow.clockwise':1056 'asymmetr':977,981 'attach':715 'badg':420 'bannerview':947 'bare':1254 'base':481,489,575,1227 'bell.fill':1049 'bind':1272 'blenddur':326,334 'blue':1196,1208 'blurreplac':972,973 'bodi':24,186,394,528,628,803,889,1456 'boilerpl':714,727 'bottom':985 'bounc':294,303,312,324,332,469,566,814,831,1051,1095 'bounceview':620 'bounci':33,310,314,449,502,1334 'bound':199,408 'brand':1175 'breath':1077,1102 'bridg':1545 'broad':1263 'build':34 'built':956 'built-in':955 'button':631,809,826 'buttonstyl':687,842 'bylay':1106 'card':822,839 'case':520,556,561,568 'caseiter':519 'categori':175,176 'caus':1359 'cgfloat':744 'cgrect':749 'chain':1083 'chang':191,200,245,353,409,582,996,1046,1268,1451,1511 'check':1488 'checklist':162,165,1436 'choos':259 'choreographi':216 'circl':439,535 'closur':368,1300,1309 'cloud.sun.rain.fill':1217 'color':1081,1121,1131,1142,1152,1158,1170,1176,1180,1199,1214,1221 'column':895 'combin':952 'common':156,159,1250 'common-mistak':158 'complet':377,388 'completioncriteria':384 'complex':212 'comput':772,1304 'condit':1064 'configur':19,28 'confirm':346 'conform':761 'constant':267 'contain':932,1434 'content':62,239,367,427,429,644,647,995,1289,1308,1391 'content.opacity':1291 'contenttransit':133,137,240,988,1002,1019,1386,1398,1408,1495 'contenttransition-io':136 'control':936,1115 'core':1543 'correct':52,351,1271,1281,1376,1402 'count':1397,1407,1415 'countsdown':1004,1027,1400,1410 'cubickeyfram':663,669,673,693 'curv':256,262,265,515,695,1438 'custom':251,319,1169,1177,1198 'customanim':252,1525 'cycl':506,579 'damp':477 'dampingfract':339 'dampingratio':485,493 'deadlin':1370 'default':1134 'defin':1156,1213 'depth':1147,1149,1189 'destin':876,927,1429 'detail':1535 'detailcard':819 'differ':460 'direct':1108 'disappear':1100 'discret':508,1042 'dispatchqueu':1364,1490 'dispatchqueue.main.asyncafter':1369 'doc':1241 'doubl':609,613,617,734,737,740 'draw':751 'drive':1516 'driven':183,222 'durat':270,275,280,297,306,315,322,330,382,467,559,564,571,593,661,665,671,675,681,685,812,829 'eas':1444 'easein':269,558,1270,1278,1288 'easeinout':279 'easeout':274,570 'edg':950,965 'effect':141,146,1032,1040,1094,1111,1125,1395,1534 'element':218,787 'els':825 'enabl':359 'end':278,285 'enum':517 'environ':1326 'everi':1312 'exact':844,1470 'exclud':767 'execut':1311 'expens':362,1296 'explicit':15,69,73,189,370,1466 'extrabounc':299,308,317 'fals':626,801,817,1005 'file':1162 'fill':1228 'fit':1508 'fix':44,1157 'fluid':295 'font':637 'foreach':900 'foregroundstyl':421,1174,1195,1207 'forget':1383 'form':458 'four':456 'frame':536,1313 'frequenc':733,754 'full':1527 'func':745 'gallerydetail':905 'galleryitem':887 'galleryview':879 'geometri':781 'green':423,1209 'griditem':896 'group':806 'grow':522,543,562 'guidanc':1542 'handl':57 'heavi':1303 'height':539 'hero':223 'herospac':796,824,841 'heroview':791 'hierarch':1140,1183,1194 'hold':1065 'icon':1137,1181 'id':821,838,848,919,1345,1358,1474,1478 'ident':971,1023 'identifi':172 'idl':521,557 'imag':634,1013,1047,1054,1066,1073,1084,1190,1202,1215,1233 'implement':4 'implicit':20,75,78,390,1452,1504 'implicit-anim':77 'improv':7 'in-plac':241,992 'includ':776 'indefinit':1061 'independ':602 'initi':457 'initialvalu':640 'inner':931 'insert':235,941,982 'insertion/removal':998 'insid':365,1298,1432 'instant':700 'instead':1483 'intend':1166 'intent':1440 'interactivespr':336 'interpol':857,1024 'io':39,82,87,90,94,97,101,109,113,118,124,127,131,134,138,142,147,378,454,504,596,778,863,934,989,1033 'isact':422,431,435,441,445,451,1071,1078,1091 'isexpand':386,800,808,816,833 'isexpanded.toggle':375 'ismut':1015 'isplay':1092 'isrecord':1079 'issearch':1072 'isvis':1280,1292,1374,1380 'item':886,901,902,906,907,916,917 'item.id':911,920 'itemthumbnail':915 'jump':701 'keyfram':210,655,689,707 'keyframeanim':96,100,211,595,639,1306 'keyframeanimator-io':99 'keyframetrack':657,667,677 'label':633,818,835,914 'largetitl':638 'layer':1119,1145,1160,1172,1187,1201 'layout':221,1361 'layout-driven':220 'lazyvgrid':894 'let':885 'lifecycl':233 'linear':266,692 'linearkeyfram':679,683,691 'linewidth':743 'loadcont':389 'logicallycomplet':385 'loop':706 'macro':104,107,710,1481 'mainactor':1513 'manual':712,725,1485 'mass':473 'match':1138,1439,1477 'matchedgeometryeffect':108,112,219,777,820,837,1339,1468 'matchedgeometryeffect-io':111 'matchedtransitionsourc':226,866,918 'mechan':1446 'mental':461 'mic.fill':1075 'minimum':898 'mirror':497 'miss':1321 'mistak':157,160,1251 'mode':151,155,1114,1124 'model':462 'modern':48 'modifi':193,398,417,1286,1459 'monochrom':1129 'motion':291,358,1323 'move':949,964 'movekeyfram':699 'multi':202,207,214,1179 'multi-color':1178 'multi-phas':201 'multi-properti':213 'multi-step':206 'multicolor':1153,1210,1219 'multipl':599,1151,1338 'must':760,849 'namespac':793,881,1479 'narrowest':1503 'natur':290,1443 'navig':115,121,225,860 'navigation-zoom-transition-io':120 'navigationlink':904 'navigationstack':229,892 'navigationtransit':227,872,908,924,1417,1422 'need':1258 'never':775,1301 'non':769 'non-animat':768 'none':1333 'notificationcount':1053 'novel':254 'numerictext':1003,1026,1028,1399,1409 'offset':650,968 'one':578,845,1275,1342,1471 'opac':434,444,546,616,653,678,954,959,987,1025,1144,1186 'option':976,1089 'outermost':1428 'outsid':1315 'pair':865,1403,1497 'palett':1167,1168,1197,1206 'path':746,750 'pattern':61,1546 'per':512,580,847,1110,1159,1171,1200,1344,1473 'per-phas':511 'percentag':1226 'percentage-bas':1225 'perceptu':464 'perform':1541 'person.crop.circle.badge.plus':1204 'phase':35,203,509,513,533,542,547,552,555,588,739,756 'phaseanim':89,93,204,503,531,584,1307 'phaseanimator-io':92 'physic':471,698 'place':243,994 'plain':688,843 'play':313 'precis':1259 'precomput':1314 'prefer':288,392,465 'preset':287,496,499 'privat':623,794,798,882,1328 'properti':215,600,722,759,771,773,1319 'protocol':253,1526 'puls':1070,1096 'pulsephas':518 'pulsephase.allcases':532,585 'pulsingdot':525 'push':966,983 'push/pop':230 'rect':748 'reduc':357,1322 'reducemot':1330,1332 'refer':166,167,1520 'references/animation-advanced.md':1522,1523 'references/core-animation-bridge.md':1547,1548 'refreshcount':1060 'remov':237,943,986 'render':150,154,1113 'repeat':703,1090 'replac':711,724,1101 'replace.downup':1021 'respons':304,337,480,483 'response-bas':479 'review':5,41,161,164,1435 'review-checklist':163 'rotat':1103 'rule':757 'run':364,577,1302 'scale':608,658,961,962,1098 'scaleeffect':430,440,541,648 'scope':1105,1260,1282,1460,1506 'score':1001,1009 'scrollview':893 'secondari':424 'see':1521 'select':192,1285,1458 'semant':1039 'sendabl':1519 'sequenc':205 'settl':488 'settling-bas':487 'settlingdur':491 'sf':248,1011,1036,1117 'shape':731 'share':217,786 'shared-el':785 'showbann':946 'showdetail':1335 'shrink':523,548,569 'signal':1229 'signalstrength':1238 'simpl':196,405,1136 'singl':1130,1141,1184 'skill' 'skill-swiftui-animation' 'slide':960 'slow':272,277,282 'small':302 'smooth':31,292,296,381,500,694 'snappi':32,301,305,426,501,1007,1413 'sosumi.ai':1244,1248 'sosumi.ai/documentation/swiftui/symbolrenderingmode)':1243 'sosumi.ai/documentation/swiftui/view/symbolrenderingmode(_:))':1247 'sourc':869,1340,1472 'source-dpearson2699' 'sourceid':910,1424 'speaker.slash':1016 'speaker.wave':1017 'speaker.wave.3.fill':1086,1192 'specif':397 'speed':268 'spring':29,80,85,286,320,321,328,329,374,452,466,472,482,490,563,592,697,811,828,1441,1528 'spring-type-io':84 'spring.delay':1378 'springkeyfram':659,696 'star.fill':636 'start':273,283 'state':182,190,352,622,797,1450,1510 'state-driven':181 'step':170,208,257,341 'stiff':475 'store':721,758 'strength':1230 'struct':524,605,619,729,790,878 'subtl':1148 'support':1324 'swift':59,263,372,419,438,463,516,583,604,723,789,877,944,979,999,1041,1182,1232,1261,1325,1367,1387 'swiftui':2,8,37,45 'swiftui-anim':1 'switch':554 'symbol':140,145,149,153,246,249,1012,1031,1037,1112,1118,1533 'symbol-effects-io':144 'symbol-rendering-mod':152 'symboleffect':247,974,975,1020,1030,1050,1057,1069,1076,1087 'symbolrenderingmod':1123,1193,1205,1218,1242,1246 'symbolvariablecolor':1222,1236 'synchron':780 'system':1155,1212,1537 'system-defin':1154,1211 'systemnam':635,1014,1048,1055,1067,1074,1085,1191,1203,1216,1234 'tapcount':587 'test':354 'text':238,1000,1139,1396,1406 'text/number':244 'three':495 'thumbnailcard':836 'time':53,255,264,854,1351 'timelin':603 'tint':1185 'togeth':418 'toolbar':1135 'top':951 'topic-accessibility' 'topic-agent-skills' 'topic-ai-coding' 'topic-apple' 'topic-claude-code' 'topic-codex-skills' 'topic-cursor-skills' 'topic-ios' 'topic-ios-development' 'topic-liquid-glass' 'topic-localization' 'topic-mapkit' 'transact':1536 'transit':11,54,117,123,126,130,224,234,862,933,948,978,980,1392,1492,1494,1531 'transitions-io':129 'triag':63,66,168 'triage-workflow':65 'trigger':348,574,581,586,625,642,643,1043 'trigger-bas':573 'trigger.toggle':632 'true':387,704,834,1336,1375,1381,1401,1411 'two':1352 'type':81,86,453,690,718,958,1022,1163,1517,1532,1539 'uiview.animate':1366,1491 'undefin':1360 'uniform':1133 'unitcurv':1538 'use':12,58,180,401,702,753,764,1128,1253,1363,1454,1476,1482,1493,1501 'valu':27,188,198,403,407,450,645,1008,1029,1045,1052,1059,1223,1237,1276,1279,1414,1463,1467 'value-bound':197,406 'value.opacity':654 'value.scale':649 'value.yoffset':652 'var':527,607,611,615,624,627,732,735,738,742,795,799,802,883,888,1329 'vari':1109 'variabl':1080,1220 'variablecolor':1097 'variablecolor.iterative.reversing.diminactivelayers':1088 'variant':576,1529 'vectorarithmet':763 'verifi':345,360 'via':1173 'view':232,414,526,530,621,630,783,792,805,846,870,880,891,928,938,1267,1343,1354,1420,1430 'visibl':311,851,1348,1353 'visual':1318 'volum':1231 'wave':752 'waveshap':730 'weather':1161 'wholesymbol':1107 'width':537 'wifi':1068,1235 'wiggl':1104 'wiggle.clockwise':1058 'withanim':18,68,72,184,369,373,380,810,827,1331,1373,1377,1447 'withanimation-explicit-anim':71 'without':997,1150 'work':363,859,1297 'workflow':64,67,169 'wrap':1448 'write':42 'wrong':1368,1388,1419 'x':969 'y':651,970 'yoffset':612,668 'zoom':116,122,228,231,861,873,909,1423,1475 'zoomspac':884,913,922","prices":[{"id":"23edced4-f139-4192-ac55-b39ce404db90","listingId":"1c4aa5d6-c8d1-4752-8e09-62039fd856d4","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:32:34.027Z"}],"sources":[{"listingId":"1c4aa5d6-c8d1-4752-8e09-62039fd856d4","source":"github","sourceId":"dpearson2699/swift-ios-skills/swiftui-animation","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swiftui-animation","isPrimary":false,"firstSeenAt":"2026-04-18T22:01:23.312Z","lastSeenAt":"2026-05-18T18:53:45.515Z"},{"listingId":"1c4aa5d6-c8d1-4752-8e09-62039fd856d4","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/swiftui-animation","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-animation","isPrimary":true,"firstSeenAt":"2026-04-18T20:32:34.027Z","lastSeenAt":"2026-05-07T22:40:29.882Z"}],"details":{"listingId":"1c4aa5d6-c8d1-4752-8e09-62039fd856d4","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"swiftui-animation","github":{"repo":"dpearson2699/swift-ios-skills","stars":599,"topics":["accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills","cursor-skills","ios","ios-development","liquid-glass","localization","mapkit","networking","storekit","swift","swift-concurrency","swiftdata","swiftui","widgetkit","xcode"],"license":"other","html_url":"https://github.com/dpearson2699/swift-ios-skills","pushed_at":"2026-04-26T21:04:17Z","description":"Agent Skills for iOS 26+, Swift 6.3, SwiftUI, and modern Apple frameworks","skill_md_sha":"fe17dacee93a5d81fd2380af6c615f4d96983105","skill_md_path":"skills/swiftui-animation/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swiftui-animation"},"layout":"multi","source":"github","category":"swift-ios-skills","frontmatter":{"name":"swiftui-animation","description":"Implement, review, or improve SwiftUI animations and transitions. Use when adding explicit animations with withAnimation, configuring implicit animations with .animation(_:body:) or .animation(_:value:), configuring spring animations (.smooth, .snappy, .bouncy), building phase or keyframe animations with PhaseAnimator/KeyframeAnimator, creating hero transitions with matchedGeometryEffect or matchedTransitionSource, adding SF Symbol effects (bounce, pulse, variableColor, breathe, rotate, wiggle), implementing custom Transition or CustomAnimation types, or ensuring animations respect accessibilityReduceMotion."},"skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-animation"},"updatedAt":"2026-05-18T18:53:45.515Z"}}