{"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        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            .onTapGesture { trigger.toggle() }\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        if isExpanded {\n            DetailCard()\n                .matchedGeometryEffect(id: \"card\", in: heroSpace)\n                .onTapGesture {\n                    withAnimation(.spring(duration: 0.4, bounce: 0.2)) {\n                        isExpanded = false\n                    }\n                }\n        } else {\n            ThumbnailCard()\n                .matchedGeometryEffect(id: \"card\", in: heroSpace)\n                .onTapGesture {\n                    withAnimation(.spring(duration: 0.4, bounce: 0.2)) {\n                        isExpanded = true\n                    }\n                }\n        }\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.684","qualityRationale":"deterministic score 0.68 from registry signals: · indexed on github topic:agent-skills · 468 github stars · SKILL.md body (15,378 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-04-22T06:53:46.138Z","embedding":null,"createdAt":"2026-04-18T20:32:34.027Z","updatedAt":"2026-04-22T06:53:46.138Z","lastSeenAt":"2026-04-22T06:53:46.138Z","tsv":"'-30':667 '/documentation/swiftui/symbolrenderingmode)':1237 '/documentation/swiftui/view/symbolrenderingmode(_:))':1241 '0':671 '0.0':300,327,335,614,1231,1286 '0.1':309 '0.15':338,679 '0.2':318,333,560,669,817,833 '0.25':683 '0.3':271,276,281,325,470,567,572,659 '0.35':383 '0.4':307,565,594,663,673,815,831 '0.5':298,316,323,468,484,549,1364,1371 '0.6':331,447,677 '0.7':437,486 '0.8':494 '0.86':340 '1':171,1244 '1.0':433,436,443,446,474,492,545,550,610,618,661,681,1232,1285 '1.15':432 '1.2':442 '1.4':544 '1.5':657 '10.0':478 '100':891 '100.0':476 '14':110,114,776 '16':135,139,982 '17':83,88,91,95,98,102,128,132,143,148,379,455,505,597,927,1026 '18':119,125,856 '2':258,1287 '26':40 '3':342,1010,1312 '4':1329 '40':538,540 '5':1354 '6':1374 '6.3':60 '7':1408 'access':56,356 'accessibilityreducemot':1319,1479 'activ':1054 'ad':14 'adapt':889 'along':601 'amplitud':733,752 'anchor':955 '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,705,785,931,983,998,1027,1247,1261,1265,1269,1275,1279,1291,1308,1376,1382,1397,1404,1429,1445,1447,1454,1491,1497,1501,1507,1536 'animat':103,106,416,706,717,725,767,1472 'animatable-macro':105 'animatabledata':710,723,1478 'animatableignor':738,762 'animation-driv':1506 'animvalu':606,638 'api':50,177 'appear':1091 'appl':1156 'appli':47,343,915,1124,1256,1413 'arrow.clockwise':1048 'asymmetr':969,973 'attach':712 'badg':420 'bannerview':939 'bare':1246 'base':481,489,575,1219 'bell.fill':1041 'bind':1264 'blenddur':326,334 'blue':1188,1200 'blurreplac':964,965 'bodi':24,186,394,528,628,800,881,1448 'boilerpl':711,724 'bottom':977 'bounc':294,303,312,324,332,469,566,816,832,1043,1087 'bounceview':620 'bounci':33,310,314,449,502,1326 'bound':199,408 'brand':1167 'breath':1069,1094 'bridg':1537 'broad':1255 'build':34 'built':948 'built-in':947 'bylay':1098 'card':808,824 'case':520,556,561,568 'caseiter':519 'categori':175,176 'caus':1351 'cgfloat':741 'cgrect':746 'chain':1075 'chang':191,200,245,353,409,582,988,1038,1260,1443,1503 'check':1480 'checklist':162,165,1428 'choos':259 'choreographi':216 'circl':439,535 'closur':368,1292,1301 'cloud.sun.rain.fill':1209 'color':1073,1113,1123,1134,1144,1150,1162,1168,1172,1191,1206,1213 'column':887 'combin':944 'common':156,159,1242 'common-mistak':158 'complet':377,388 'completioncriteria':384 'complex':212 'comput':769,1296 'condit':1056 'configur':19,28 'confirm':346 'conform':758 'constant':267 'contain':924,1426 'content':62,239,367,427,429,641,644,987,1281,1300,1383 'content.opacity':1283 'contenttransit':133,137,240,980,994,1011,1378,1390,1400,1487 'contenttransition-io':136 'control':928,1107 'core':1535 'correct':52,351,1263,1273,1368,1394 'count':1389,1399,1407 'countsdown':996,1019,1392,1402 'cubickeyfram':660,666,670,690 'curv':256,262,265,515,692,1430 'custom':251,319,1161,1169,1190 'customanim':252,1517 'cycl':506,579 'damp':477 'dampingfract':339 'dampingratio':485,493 'deadlin':1362 'default':1126 'defin':1148,1205 'depth':1139,1141,1181 'destin':868,919,1421 'detail':1527 'detailcard':805 'differ':460 'direct':1100 'disappear':1092 'discret':508,1034 'dispatchqueu':1356,1482 'dispatchqueue.main.asyncafter':1361 'doc':1233 'doubl':609,613,617,731,734,737 'draw':748 'drive':1508 'driven':183,222 'durat':270,275,280,297,306,315,322,330,382,467,559,564,571,593,658,662,668,672,678,682,814,830 'eas':1436 'easein':269,558,1262,1270,1280 'easeinout':279 'easeout':274,570 'edg':942,957 'effect':141,146,1024,1032,1086,1103,1117,1387,1526 'element':218,784 'els':820 'enabl':359 'end':278,285 'enum':517 'environ':1318 'everi':1304 'exact':836,1462 'exclud':764 'execut':1303 'expens':362,1288 'explicit':15,69,73,189,370,1458 'extrabounc':299,308,317 'fals':626,798,819,997 'file':1154 'fill':1220 'fit':1500 'fix':44,1149 'fluid':295 'font':634 'foreach':892 'foregroundstyl':421,1166,1187,1199 'forget':1375 'form':458 'four':456 'frame':536,1305 'frequenc':730,751 'full':1519 'func':742 'gallerydetail':897 'galleryitem':879 'galleryview':871 'geometri':778 'green':423,1201 'griditem':888 'grow':522,543,562 'guidanc':1534 'handl':57 'heavi':1295 'height':539 'hero':223 'herospac':793,810,826 'heroview':788 'hierarch':1132,1175,1186 'hold':1057 'icon':1129,1173 'id':807,823,840,911,1337,1350,1466,1470 'ident':963,1015 'identifi':172 'idl':521,557 'imag':631,1005,1039,1046,1058,1065,1076,1182,1194,1207,1225 'implement':4 'implicit':20,75,78,390,1444,1496 'implicit-anim':77 'improv':7 'in-plac':241,984 'includ':773 'indefinit':1053 'independ':602 'initi':457 'initialvalu':637 'inner':923 'insert':235,933,974 'insertion/removal':990 'insid':365,1290,1424 'instant':697 'instead':1475 'intend':1158 'intent':1432 'interactivespr':336 'interpol':849,1016 'io':39,82,87,90,94,97,101,109,113,118,124,127,131,134,138,142,147,378,454,504,596,775,855,926,981,1025 'isact':422,431,435,441,445,451,1063,1070,1083 'isexpand':386,797,804,818,834 'isexpanded.toggle':375 'ismut':1007 'isplay':1084 'isrecord':1071 'issearch':1064 'isvis':1272,1284,1366,1372 'item':878,893,894,898,899,908,909 'item.id':903,912 'itemthumbnail':907 'jump':698 'keyfram':210,652,686,704 'keyframeanim':96,100,211,595,636,1298 'keyframeanimator-io':99 'keyframetrack':654,664,674 'label':906 'largetitl':635 'layer':1111,1137,1152,1164,1179,1193 'layout':221,1353 'layout-driven':220 'lazyvgrid':886 'let':877 'lifecycl':233 'linear':266,689 'linearkeyfram':676,680,688 'linewidth':740 'loadcont':389 'logicallycomplet':385 'loop':703 'macro':104,107,707,1473 'mainactor':1505 'manual':709,722,1477 'mass':473 'match':1130,1431,1469 'matchedgeometryeffect':108,112,219,774,806,822,1331,1460 'matchedgeometryeffect-io':111 'matchedtransitionsourc':226,858,910 'mechan':1438 'mental':461 'mic.fill':1067 'minimum':890 'mirror':497 'miss':1313 'mistak':157,160,1243 'mode':151,155,1106,1116 'model':462 'modern':48 'modifi':193,398,417,1278,1451 'monochrom':1121 'motion':291,358,1315 'move':941,956 'movekeyfram':696 'multi':202,207,214,1171 'multi-color':1170 'multi-phas':201 'multi-properti':213 'multi-step':206 'multicolor':1145,1202,1211 'multipl':599,1143,1330 'must':757,841 'namespac':790,873,1471 'narrowest':1495 'natur':290,1435 'navig':115,121,225,852 'navigation-zoom-transition-io':120 'navigationlink':896 'navigationstack':229,884 'navigationtransit':227,864,900,916,1409,1414 'need':1250 'never':772,1293 'non':766 'non-animat':765 'none':1325 'notificationcount':1045 'novel':254 'numerictext':995,1018,1020,1391,1401 'offset':647,960 'one':578,837,1267,1334,1463 'ontapgestur':684,811,827 'opac':434,444,546,616,650,675,946,951,979,1017,1136,1178 'option':968,1081 'outermost':1420 'outsid':1307 'pair':857,1395,1489 'palett':1159,1160,1189,1198 'path':743,747 'pattern':61,1538 'per':512,580,839,1102,1151,1163,1192,1336,1465 'per-phas':511 'percentag':1218 'percentage-bas':1217 'perceptu':464 'perform':1533 'person.crop.circle.badge.plus':1196 'phase':35,203,509,513,533,542,547,552,555,588,736,753 'phaseanim':89,93,204,503,531,584,1299 'phaseanimator-io':92 'physic':471,695 'place':243,986 'play':313 'precis':1251 'precomput':1306 'prefer':288,392,465 'preset':287,496,499 'privat':623,791,795,874,1320 'properti':215,600,719,756,768,770,1311 'protocol':253,1518 'puls':1062,1088 'pulsephas':518 'pulsephase.allcases':532,585 'pulsingdot':525 'push':958,975 'push/pop':230 'rect':745 'reduc':357,1314 'reducemot':1322,1324 'refer':166,167,1512 'references/animation-advanced.md':1514,1515 'references/core-animation-bridge.md':1539,1540 'refreshcount':1052 'remov':237,935,978 'render':150,154,1105 'repeat':700,1082 'replac':708,721,1093 'replace.downup':1013 'respons':304,337,480,483 'response-bas':479 'review':5,41,161,164,1427 'review-checklist':163 'rotat':1095 'rule':754 'run':364,577,1294 'scale':608,655,953,954,1090 'scaleeffect':430,440,541,645 'scope':1097,1252,1274,1452,1498 'score':993,1001 'scrollview':885 'secondari':424 'see':1513 'select':192,1277,1450 'semant':1031 'sendabl':1511 'sequenc':205 'settl':488 'settling-bas':487 'settlingdur':491 'sf':248,1003,1028,1109 'shape':728 'share':217,783 'shared-el':782 'showbann':938 'showdetail':1327 'shrink':523,548,569 'signal':1221 'signalstrength':1230 'simpl':196,405,1128 'singl':1122,1133,1176 'skill' 'skill-swiftui-animation' 'slide':952 'slow':272,277,282 'small':302 'smooth':31,292,296,381,500,691 'snappi':32,301,305,426,501,999,1405 'sosumi.ai':1236,1240 'sosumi.ai/documentation/swiftui/symbolrenderingmode)':1235 'sosumi.ai/documentation/swiftui/view/symbolrenderingmode(_:))':1239 'sourc':861,1332,1464 'source-dpearson2699' 'sourceid':902,1416 'speaker.slash':1008 'speaker.wave':1009 'speaker.wave.3.fill':1078,1184 'specif':397 'speed':268 'spring':29,80,85,286,320,321,328,329,374,452,466,472,482,490,563,592,694,813,829,1433,1520 'spring-type-io':84 'spring.delay':1370 'springkeyfram':656,693 'star.fill':633 'start':273,283 'state':182,190,352,622,794,1442,1502 'state-driven':181 'step':170,208,257,341 'stiff':475 'store':718,755 'strength':1222 'struct':524,605,619,726,787,870 'subtl':1140 'support':1316 'swift':59,263,372,419,438,463,516,583,604,720,786,869,936,971,991,1033,1174,1224,1253,1317,1359,1379 'swiftui':2,8,37,45 'swiftui-anim':1 'switch':554 'symbol':140,145,149,153,246,249,1004,1023,1029,1104,1110,1525 'symbol-effects-io':144 'symbol-rendering-mod':152 'symboleffect':247,966,967,1012,1022,1042,1049,1061,1068,1079 'symbolrenderingmod':1115,1185,1197,1210,1234,1238 'symbolvariablecolor':1214,1228 'synchron':777 'system':1147,1204,1529 'system-defin':1146,1203 'systemnam':632,1006,1040,1047,1059,1066,1077,1183,1195,1208,1226 'tapcount':587 'test':354 'text':238,992,1131,1388,1398 'text/number':244 'three':495 'thumbnailcard':821 'time':53,255,264,846,1343 'timelin':603 'tint':1177 'togeth':418 'toolbar':1127 'top':943 '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':1528 'transit':11,54,117,123,126,130,224,234,854,925,940,970,972,1384,1484,1486,1523 'transitions-io':129 'triag':63,66,168 'triage-workflow':65 'trigger':348,574,581,586,625,639,640,1035 'trigger-bas':573 'trigger.toggle':685 'true':387,701,835,1328,1367,1373,1393,1403 'two':1344 'type':81,86,453,687,715,950,1014,1155,1509,1524,1531 'uiview.animate':1358,1483 'undefin':1352 'uniform':1125 'unitcurv':1530 'use':12,58,180,401,699,750,761,1120,1245,1355,1446,1468,1474,1485,1493 'valu':27,188,198,403,407,450,642,1000,1021,1037,1044,1051,1215,1229,1268,1271,1406,1455,1459 'value-bound':197,406 'value.opacity':651 'value.scale':646 'value.yoffset':649 'var':527,607,611,615,624,627,729,732,735,739,792,796,799,875,880,1321 'vari':1101 'variabl':1072,1212 'variablecolor':1089 'variablecolor.iterative.reversing.diminactivelayers':1080 'variant':576,1521 'vectorarithmet':760 'verifi':345,360 'via':1165 'view':232,414,526,530,621,630,780,789,802,838,862,872,883,920,930,1259,1335,1346,1412,1422 'visibl':311,843,1340,1345 'visual':1310 'volum':1223 'wave':749 'waveshap':727 'weather':1153 'wholesymbol':1099 'width':537 'wifi':1060,1227 'wiggl':1096 'wiggle.clockwise':1050 'withanim':18,68,72,184,369,373,380,812,828,1323,1365,1369,1439 'withanimation-explicit-anim':71 'without':989,1142 'work':363,851,1289 'workflow':64,67,169 'wrap':1440 'write':42 'wrong':1360,1380,1411 'x':961 'y':648,962 'yoffset':612,665 'zoom':116,122,228,231,853,865,901,1415,1467 'zoomspac':876,905,914","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-04-22T06:53:46.138Z"},{"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-04-22T06:40:30.361Z"}],"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":468,"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-21T19:26:16Z","description":"Agent Skills for iOS 26+, Swift 6.3, SwiftUI, and modern Apple frameworks","skill_md_sha":"90e7d6addc7d2247981af6229d0c529baa15a897","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-04-22T06:53:46.138Z"}}