agy-superpowers 5.0.8 → 5.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/agent/rules/superpowers.md +54 -0
- package/template/agent/skills/frontend-developer/SKILL.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-nextjs.md +343 -0
- package/template/agent/skills/frontend-developer/references/react-rules/_sections.md +46 -0
- package/template/agent/skills/frontend-developer/references/react-rules/_template.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-event-handler-refs.md +55 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-init-once.md +42 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-use-latest.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-api-routes.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-defer-await.md +80 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-dependencies.md +51 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-parallel.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-suspense-boundaries.md +99 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-barrel-imports.md +59 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-conditional.md +31 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-defer-third-party.md +49 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-dynamic-imports.md +35 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-preload.md +50 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-event-listeners.md +74 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-localstorage-schema.md +71 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-passive-event-listeners.md +48 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-swr-dedup.md +56 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-batch-dom-css.md +107 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-function-results.md +80 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-property-access.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-storage.md +70 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-combine-iterations.md +32 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-early-exit.md +50 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-flatmap-filter.md +60 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-hoist-regexp.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-index-maps.md +37 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-length-check-first.md +49 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-min-max-loop.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-set-map-lookups.md +24 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-tosorted-immutable.md +57 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-activity.md +26 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-animate-svg-wrapper.md +47 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-conditional-render.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-content-visibility.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hoist-jsx.md +46 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hydration-no-flicker.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hydration-suppress-warning.md +30 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-resource-hints.md +85 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-script-defer-async.md +68 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-svg-precision.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-usetransition-loading.md +75 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-defer-reads.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-dependencies.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-derived-state-no-effect.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-derived-state.md +29 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-functional-setstate.md +74 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-lazy-state-init.md +58 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-memo-with-default-value.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-memo.md +44 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-move-effect-to-event.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-no-inline-components.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-simple-expression-in-memo.md +35 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-split-combined-hooks.md +64 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-transitions.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-use-deferred-value.md +59 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-use-ref-transient-values.md +73 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-after-nonblocking.md +73 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-auth-actions.md +96 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-cache-lru.md +41 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-cache-react.md +76 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-dedup-props.md +65 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-hoist-static-io.md +142 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-parallel-fetching.md +83 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-serialization.md +38 -0
- package/template/agent/skills/frontend-developer/references/svelte-sveltekit.md +220 -0
- package/template/agent/skills/frontend-developer/references/vanilla.md +275 -0
- package/template/agent/skills/frontend-developer/references/vue-nuxt.md +289 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/_index.md +154 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/animation-class-based-technique.md +254 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/animation-state-driven-technique.md +291 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-async.md +97 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-data-flow.md +307 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-fallthrough-attrs.md +174 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-keep-alive.md +137 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-slots.md +216 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-suspense.md +228 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-teleport.md +108 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-transition-group.md +128 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-transition.md +125 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/composables.md +290 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/directives.md +162 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-v-once-v-memo-directives.md +182 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-virtualize-large-lists.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/plugins.md +166 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/reactivity.md +344 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/render-functions.md +201 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/sfc.md +310 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/state-management.md +135 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/updated-hook-performance.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/_index.md +23 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-beforeenter-no-param-trigger.md +167 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-beforerouteenter-no-this.md +176 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-guard-async-await-pattern.md +227 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-navigation-guard-infinite-loop.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-navigation-guard-next-deprecated.md +150 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-param-change-no-lifecycle.md +181 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-simple-routing-cleanup.md +209 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-use-vue-router-for-production.md +183 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/_index.md +29 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/async-component-testing.md +163 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/teleport-testing-complexity.md +158 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-async-await-flushpromises.md +175 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-browser-vs-node-runners.md +208 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-component-blackbox-approach.md +144 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-composables-helper-wrapper.md +238 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-e2e-playwright-recommended.md +242 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-no-snapshot-only.md +197 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-pinia-store-setup.md +228 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-suspense-async-components.md +229 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-vitest-recommended-for-vue.md +204 -0
- package/template/agent/skills/mobile-developer/SKILL.md +52 -0
- package/template/agent/skills/mobile-developer/references/android-native.md +396 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-accessibility.md +36 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-architecture.md +52 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-coroutines.md +139 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-data-layer.md +51 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-emulator-skill.md +108 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-gradle-logic.md +126 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-retrofit.md +142 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-testing.md +102 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-viewmodel.md +43 -0
- package/template/agent/skills/mobile-developer/references/android-rules/coil-compose.md +74 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-navigation.md +422 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-performance-audit.md +199 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-ui.md +49 -0
- package/template/agent/skills/mobile-developer/references/android-rules/gradle-build-performance.md +346 -0
- package/template/agent/skills/mobile-developer/references/android-rules/kotlin-concurrency-expert.md +169 -0
- package/template/agent/skills/mobile-developer/references/android-rules/rxjava-to-coroutines-migration.md +101 -0
- package/template/agent/skills/mobile-developer/references/android-rules/xml-to-compose-migration.md +338 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-best-practices.md +52 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-checks-migration.md +134 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-cli-app-best-practices.md +123 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-doc-validation.md +45 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-matcher-best-practices.md +106 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-modern-features.md +241 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-package-maintenance.md +75 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-test-fundamentals.md +124 -0
- package/template/agent/skills/mobile-developer/references/flutter.md +291 -0
- package/template/agent/skills/mobile-developer/references/ios-native.md +358 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/accessibility-patterns.md +215 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-advanced.md +403 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-basics.md +284 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-transitions.md +326 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/charts-accessibility.md +135 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/charts.md +602 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/image-optimization.md +203 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/latest-apis.md +464 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/layout-best-practices.md +266 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/liquid-glass.md +416 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/list-patterns.md +394 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-scenes.md +318 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-views.md +357 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-window-styling.md +303 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/performance-patterns.md +403 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/scroll-patterns.md +293 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/sheet-navigation-patterns.md +363 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/state-management.md +417 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/view-structure.md +389 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/_sections.md +86 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/_template.md +28 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-derived-value.md +53 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-gesture-detector-press.md +95 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-gpu-properties.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/design-system-compound-components.md +66 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/fonts-config-plugin.md +71 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/imports-design-system-folder.md +68 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/js-hoist-intl.md +61 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-callbacks.md +44 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-function-references.md +132 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-images.md +53 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-inline-objects.md +97 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-expensive.md +94 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-memo.md +82 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-types.md +104 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-virtualize.md +67 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/monorepo-native-deps-in-app.md +46 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/monorepo-single-dependency-versions.md +63 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/navigation-native-navigators.md +188 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-compiler-destructure-functions.md +50 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-compiler-reanimated-shared-values.md +48 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-dispatcher.md +91 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-fallback.md +56 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-minimize.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/rendering-no-falsy-and.md +74 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/rendering-text-in-text-component.md +36 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/scroll-position-no-state.md +82 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/state-ground-truth.md +80 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-expo-image.md +66 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-image-gallery.md +104 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-measure-views.md +78 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-menus.md +174 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-native-modals.md +77 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-pressable.md +61 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-safe-area-scroll.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-scrollview-content-inset.md +45 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-styling.md +87 -0
- package/template/agent/skills/mobile-developer/references/react-native.md +345 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# SwiftUI Accessibility Patterns Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Core Principle](#core-principle)
|
|
6
|
+
- [Dynamic Type and @ScaledMetric](#dynamic-type-and-scaledmetric)
|
|
7
|
+
- [Accessibility Traits](#accessibility-traits)
|
|
8
|
+
- [Decorative Images](#decorative-images)
|
|
9
|
+
- [Element Grouping](#element-grouping)
|
|
10
|
+
- [Custom Controls](#custom-controls)
|
|
11
|
+
- [Summary Checklist](#summary-checklist)
|
|
12
|
+
|
|
13
|
+
## Core Principle
|
|
14
|
+
|
|
15
|
+
Prefer `Button` over `onTapGesture` for tappable elements. `Button` provides VoiceOver support, focus handling, and proper traits for free.
|
|
16
|
+
|
|
17
|
+
## Dynamic Type and @ScaledMetric
|
|
18
|
+
|
|
19
|
+
System text styles scale with Dynamic Type automatically. Prefer built-in styles like `.largeTitle`, `.title`, `.title2`, `.title3`, `.headline`, `.subheadline`, `.body`, `.callout`, `.footnote`, `.caption`, and `.caption2` when they fit your UI:
|
|
20
|
+
|
|
21
|
+
```swift
|
|
22
|
+
VStack(alignment: .leading) {
|
|
23
|
+
Text("Inbox")
|
|
24
|
+
.font(.title2)
|
|
25
|
+
Text("3 unread messages")
|
|
26
|
+
.font(.body)
|
|
27
|
+
Text("Updated just now")
|
|
28
|
+
.font(.caption)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For custom fonts, use a Dynamic Type-aware font initializer so the text still follows the user's preferred content size:
|
|
33
|
+
|
|
34
|
+
```swift
|
|
35
|
+
VStack(alignment: .leading) {
|
|
36
|
+
Text("Article")
|
|
37
|
+
.font(.custom("SourceSerif4-Semibold", size: 28, relativeTo: .title2))
|
|
38
|
+
Text("Body copy")
|
|
39
|
+
.font(.custom("SourceSerif4-Regular", size: 17))
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`Font.custom(_:size:relativeTo:)` lets you match a specific text style. `Font.custom(_:size:)` scales relative to the body style. Avoid fixed-size custom fonts for primary content that should respond to Dynamic Type.
|
|
44
|
+
|
|
45
|
+
For non-text numeric values like padding, spacing, and image sizes, use `@ScaledMetric`:
|
|
46
|
+
|
|
47
|
+
```swift
|
|
48
|
+
struct ProfileHeader: View {
|
|
49
|
+
@ScaledMetric private var avatarSize = 60.0
|
|
50
|
+
@ScaledMetric private var spacing = 12.0
|
|
51
|
+
|
|
52
|
+
var body: some View {
|
|
53
|
+
HStack(spacing: spacing) {
|
|
54
|
+
Image("avatar")
|
|
55
|
+
.resizable()
|
|
56
|
+
.frame(width: avatarSize, height: avatarSize)
|
|
57
|
+
Text("Username")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Specify a `relativeTo` text style when the value should track a specific Dynamic Type style, including for images or icons that should stay proportional to nearby text:
|
|
64
|
+
|
|
65
|
+
```swift
|
|
66
|
+
struct StatusRow: View {
|
|
67
|
+
@ScaledMetric(relativeTo: .body) private var iconSize = 18.0
|
|
68
|
+
|
|
69
|
+
var body: some View {
|
|
70
|
+
HStack(spacing: 8) {
|
|
71
|
+
Image(systemName: "checkmark.circle.fill")
|
|
72
|
+
.font(.system(size: iconSize))
|
|
73
|
+
Text("Synced")
|
|
74
|
+
.font(.custom("AvenirNext-Regular", size: 17, relativeTo: .body))
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Accessibility Traits
|
|
81
|
+
|
|
82
|
+
Use `accessibilityAddTraits` and `accessibilityRemoveTraits` for state-driven traits:
|
|
83
|
+
|
|
84
|
+
```swift
|
|
85
|
+
Text(item.title)
|
|
86
|
+
.accessibilityAddTraits(item.isSelected ? [.isSelected, .isButton] : .isButton)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Use `.disabled(true)` to make VoiceOver announce "Dimmed" for non-interactive elements.
|
|
90
|
+
|
|
91
|
+
## Decorative Images
|
|
92
|
+
|
|
93
|
+
Use `Image(decorative:bundle:)` when an asset image is purely visual and should not appear in the accessibility tree.
|
|
94
|
+
|
|
95
|
+
```swift
|
|
96
|
+
Image(decorative: "confetti")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This is appropriate for backgrounds, flourishes, and icons that do not add meaning beyond nearby text.
|
|
100
|
+
|
|
101
|
+
If the image conveys information, keep it accessible and provide a clear label:
|
|
102
|
+
|
|
103
|
+
```swift
|
|
104
|
+
Image("receipt")
|
|
105
|
+
.accessibilityLabel("Receipt")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For non-asset images, such as SF Symbols, hide decorative content with `accessibilityHidden(true)` instead:
|
|
109
|
+
|
|
110
|
+
```swift
|
|
111
|
+
Image(systemName: "sparkles")
|
|
112
|
+
.accessibilityHidden(true)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Element Grouping
|
|
116
|
+
|
|
117
|
+
### .combine -- Auto-join child labels
|
|
118
|
+
|
|
119
|
+
```swift
|
|
120
|
+
HStack {
|
|
121
|
+
Image(systemName: "star.fill")
|
|
122
|
+
Text("Favorites")
|
|
123
|
+
Text("(\(count))")
|
|
124
|
+
}
|
|
125
|
+
.accessibilityElement(children: .combine)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
VoiceOver reads all child labels as one element, separated by commas.
|
|
129
|
+
|
|
130
|
+
### .ignore -- Manual label for container
|
|
131
|
+
|
|
132
|
+
```swift
|
|
133
|
+
HStack {
|
|
134
|
+
Text(item.name)
|
|
135
|
+
Spacer()
|
|
136
|
+
Text(item.price)
|
|
137
|
+
}
|
|
138
|
+
.accessibilityElement(children: .ignore)
|
|
139
|
+
.accessibilityLabel("\(item.name), \(item.price)")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### .contain -- Semantic grouping
|
|
143
|
+
|
|
144
|
+
```swift
|
|
145
|
+
HStack {
|
|
146
|
+
ForEach(tabs) { tab in
|
|
147
|
+
TabButton(tab: tab)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
.accessibilityElement(children: .contain)
|
|
151
|
+
.accessibilityLabel("Tab bar")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
VoiceOver announces the container name when focus enters/exits.
|
|
155
|
+
|
|
156
|
+
## Custom Controls
|
|
157
|
+
|
|
158
|
+
### Adjustable controls (increment/decrement)
|
|
159
|
+
|
|
160
|
+
```swift
|
|
161
|
+
PageControl(selectedIndex: $selectedIndex, pageCount: pageCount)
|
|
162
|
+
.accessibilityElement()
|
|
163
|
+
.accessibilityValue("Page \(selectedIndex + 1) of \(pageCount)")
|
|
164
|
+
.accessibilityAdjustableAction { direction in
|
|
165
|
+
switch direction {
|
|
166
|
+
case .increment:
|
|
167
|
+
guard selectedIndex < pageCount - 1 else { break }
|
|
168
|
+
selectedIndex += 1
|
|
169
|
+
case .decrement:
|
|
170
|
+
guard selectedIndex > 0 else { break }
|
|
171
|
+
selectedIndex -= 1
|
|
172
|
+
@unknown default:
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Representing custom views as native controls
|
|
179
|
+
|
|
180
|
+
When a custom view should behave like a native control for accessibility:
|
|
181
|
+
|
|
182
|
+
```swift
|
|
183
|
+
HStack {
|
|
184
|
+
Text(label)
|
|
185
|
+
Toggle("", isOn: $isOn)
|
|
186
|
+
}
|
|
187
|
+
.accessibilityRepresentation {
|
|
188
|
+
Toggle(label, isOn: $isOn)
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Label-content pairing
|
|
193
|
+
|
|
194
|
+
```swift
|
|
195
|
+
@Namespace private var ns
|
|
196
|
+
|
|
197
|
+
HStack {
|
|
198
|
+
Text("Volume")
|
|
199
|
+
.accessibilityLabeledPair(role: .label, id: "volume", in: ns)
|
|
200
|
+
Slider(value: $volume)
|
|
201
|
+
.accessibilityLabeledPair(role: .content, id: "volume", in: ns)
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Summary Checklist
|
|
206
|
+
|
|
207
|
+
- [ ] Use `Button` instead of `onTapGesture` for tappable elements
|
|
208
|
+
- [ ] Use built-in text styles or Dynamic Type-aware custom fonts for text
|
|
209
|
+
- [ ] Use `@ScaledMetric` for custom values that should scale with Dynamic Type
|
|
210
|
+
- [ ] Mark purely decorative images as decorative or hidden from accessibility
|
|
211
|
+
- [ ] Group related elements with `accessibilityElement(children:)`
|
|
212
|
+
- [ ] Provide `accessibilityLabel` when default labels are unclear
|
|
213
|
+
- [ ] Use `accessibilityRepresentation` for custom controls
|
|
214
|
+
- [ ] Use `accessibilityAdjustableAction` for increment/decrement controls
|
|
215
|
+
- [ ] Ensure navigation flow is logical when using VoiceOver grouping
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# SwiftUI Advanced Animations
|
|
2
|
+
|
|
3
|
+
Transactions, phase animations (iOS 17+), keyframe animations (iOS 17+), completion handlers (iOS 17+), and `@Animatable` macro (iOS 26+).
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Transactions](#transactions)
|
|
7
|
+
- [Phase Animations (iOS 17+)](#phase-animations-ios-17)
|
|
8
|
+
- [Keyframe Animations (iOS 17+)](#keyframe-animations-ios-17)
|
|
9
|
+
- [Animation Completion Handlers (iOS 17+)](#animation-completion-handlers-ios-17)
|
|
10
|
+
- [@Animatable Macro (iOS 26+)](#animatable-macro-ios-26)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Transactions
|
|
15
|
+
|
|
16
|
+
The underlying mechanism for all animations in SwiftUI.
|
|
17
|
+
|
|
18
|
+
### Basic Usage
|
|
19
|
+
|
|
20
|
+
```swift
|
|
21
|
+
// withAnimation is shorthand for withTransaction
|
|
22
|
+
withAnimation(.default) { flag.toggle() }
|
|
23
|
+
|
|
24
|
+
// Equivalent explicit transaction
|
|
25
|
+
var transaction = Transaction(animation: .default)
|
|
26
|
+
withTransaction(transaction) { flag.toggle() }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### The .transaction Modifier
|
|
30
|
+
|
|
31
|
+
```swift
|
|
32
|
+
Rectangle()
|
|
33
|
+
.frame(width: flag ? 100 : 50, height: 50)
|
|
34
|
+
.transaction { t in
|
|
35
|
+
t.animation = .default
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Note:** This behaves like the deprecated `.animation(_:)` without value parameter - it animates on every state change.
|
|
40
|
+
|
|
41
|
+
### Animation Precedence
|
|
42
|
+
|
|
43
|
+
**Implicit animations override explicit animations** (later in view tree wins).
|
|
44
|
+
|
|
45
|
+
```swift
|
|
46
|
+
Button("Tap") {
|
|
47
|
+
withAnimation(.linear) { flag.toggle() }
|
|
48
|
+
}
|
|
49
|
+
.animation(.bouncy, value: flag) // .bouncy wins!
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Disabling Animations
|
|
53
|
+
|
|
54
|
+
```swift
|
|
55
|
+
// Prevent implicit animations from overriding
|
|
56
|
+
.transaction { t in
|
|
57
|
+
t.disablesAnimations = true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Remove animation entirely
|
|
61
|
+
.transaction { $0.animation = nil }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Custom Transaction Keys (iOS 17+)
|
|
65
|
+
|
|
66
|
+
Pass metadata through transactions.
|
|
67
|
+
|
|
68
|
+
```swift
|
|
69
|
+
struct ChangeSourceKey: TransactionKey {
|
|
70
|
+
static let defaultValue: String = "unknown"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
extension Transaction {
|
|
74
|
+
var changeSource: String {
|
|
75
|
+
get { self[ChangeSourceKey.self] }
|
|
76
|
+
set { self[ChangeSourceKey.self] = newValue }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Set source
|
|
81
|
+
var transaction = Transaction(animation: .default)
|
|
82
|
+
transaction.changeSource = "server"
|
|
83
|
+
withTransaction(transaction) { flag.toggle() }
|
|
84
|
+
|
|
85
|
+
// Read in view tree
|
|
86
|
+
.transaction { t in
|
|
87
|
+
if t.changeSource == "server" {
|
|
88
|
+
t.animation = .smooth
|
|
89
|
+
} else {
|
|
90
|
+
t.animation = .bouncy
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Phase Animations (iOS 17+)
|
|
98
|
+
|
|
99
|
+
Cycle through discrete phases automatically. Each phase change is a separate animation.
|
|
100
|
+
|
|
101
|
+
### Basic Usage
|
|
102
|
+
|
|
103
|
+
```swift
|
|
104
|
+
// GOOD - triggered phase animation
|
|
105
|
+
Button("Shake") { trigger += 1 }
|
|
106
|
+
.phaseAnimator(
|
|
107
|
+
[0.0, -10.0, 10.0, -5.0, 5.0, 0.0],
|
|
108
|
+
trigger: trigger
|
|
109
|
+
) { content, offset in
|
|
110
|
+
content.offset(x: offset)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Infinite loop (no trigger)
|
|
114
|
+
Circle()
|
|
115
|
+
.phaseAnimator([1.0, 1.2, 1.0]) { content, scale in
|
|
116
|
+
content.scaleEffect(scale)
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Enum Phases (Recommended for Clarity)
|
|
121
|
+
|
|
122
|
+
```swift
|
|
123
|
+
// GOOD - enum phases are self-documenting
|
|
124
|
+
enum BouncePhase: CaseIterable {
|
|
125
|
+
case initial, up, down, settle
|
|
126
|
+
|
|
127
|
+
var scale: CGFloat {
|
|
128
|
+
switch self {
|
|
129
|
+
case .initial: 1.0
|
|
130
|
+
case .up: 1.2
|
|
131
|
+
case .down: 0.9
|
|
132
|
+
case .settle: 1.0
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Circle()
|
|
138
|
+
.phaseAnimator(BouncePhase.allCases, trigger: trigger) { content, phase in
|
|
139
|
+
content.scaleEffect(phase.scale)
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Custom Timing Per Phase
|
|
144
|
+
|
|
145
|
+
```swift
|
|
146
|
+
.phaseAnimator([0, -20, 20], trigger: trigger) { content, offset in
|
|
147
|
+
content.offset(x: offset)
|
|
148
|
+
} animation: { phase in
|
|
149
|
+
switch phase {
|
|
150
|
+
case -20: .bouncy
|
|
151
|
+
case 20: .linear
|
|
152
|
+
default: .smooth
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Good vs Bad
|
|
158
|
+
|
|
159
|
+
```swift
|
|
160
|
+
// GOOD - use phaseAnimator for multi-step sequences
|
|
161
|
+
.phaseAnimator([0, -10, 10, 0], trigger: trigger) { content, offset in
|
|
162
|
+
content.offset(x: offset)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// BAD - manual DispatchQueue sequencing
|
|
166
|
+
Button("Animate") {
|
|
167
|
+
withAnimation(.easeOut(duration: 0.1)) { offset = -10 }
|
|
168
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
169
|
+
withAnimation { offset = 10 }
|
|
170
|
+
}
|
|
171
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
172
|
+
withAnimation { offset = 0 }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Keyframe Animations (iOS 17+)
|
|
180
|
+
|
|
181
|
+
Precise timing control with exact values at specific times.
|
|
182
|
+
|
|
183
|
+
### Basic Usage
|
|
184
|
+
|
|
185
|
+
```swift
|
|
186
|
+
Button("Bounce") { trigger += 1 }
|
|
187
|
+
.keyframeAnimator(
|
|
188
|
+
initialValue: AnimationValues(),
|
|
189
|
+
trigger: trigger
|
|
190
|
+
) { content, value in
|
|
191
|
+
content
|
|
192
|
+
.scaleEffect(value.scale)
|
|
193
|
+
.offset(y: value.verticalOffset)
|
|
194
|
+
} keyframes: { _ in
|
|
195
|
+
KeyframeTrack(\.scale) {
|
|
196
|
+
SpringKeyframe(1.2, duration: 0.15)
|
|
197
|
+
SpringKeyframe(0.9, duration: 0.1)
|
|
198
|
+
SpringKeyframe(1.0, duration: 0.15)
|
|
199
|
+
}
|
|
200
|
+
KeyframeTrack(\.verticalOffset) {
|
|
201
|
+
LinearKeyframe(-20, duration: 0.15)
|
|
202
|
+
LinearKeyframe(0, duration: 0.25)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
struct AnimationValues {
|
|
207
|
+
var scale: CGFloat = 1.0
|
|
208
|
+
var verticalOffset: CGFloat = 0
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Keyframe Types
|
|
213
|
+
|
|
214
|
+
| Type | Behavior |
|
|
215
|
+
|------|----------|
|
|
216
|
+
| `CubicKeyframe` | Smooth interpolation |
|
|
217
|
+
| `LinearKeyframe` | Straight-line interpolation |
|
|
218
|
+
| `SpringKeyframe` | Spring physics |
|
|
219
|
+
| `MoveKeyframe` | Instant jump (no interpolation) |
|
|
220
|
+
|
|
221
|
+
### Multiple Synchronized Tracks
|
|
222
|
+
|
|
223
|
+
Tracks run **in parallel**, each animating one property.
|
|
224
|
+
|
|
225
|
+
```swift
|
|
226
|
+
// GOOD - bell shake with synchronized rotation and scale
|
|
227
|
+
struct BellAnimation {
|
|
228
|
+
var rotation: Double = 0
|
|
229
|
+
var scale: CGFloat = 1.0
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Image(systemName: "bell.fill")
|
|
233
|
+
.keyframeAnimator(
|
|
234
|
+
initialValue: BellAnimation(),
|
|
235
|
+
trigger: trigger
|
|
236
|
+
) { content, value in
|
|
237
|
+
content
|
|
238
|
+
.rotationEffect(.degrees(value.rotation))
|
|
239
|
+
.scaleEffect(value.scale)
|
|
240
|
+
} keyframes: { _ in
|
|
241
|
+
KeyframeTrack(\.rotation) {
|
|
242
|
+
CubicKeyframe(15, duration: 0.1)
|
|
243
|
+
CubicKeyframe(-15, duration: 0.1)
|
|
244
|
+
CubicKeyframe(10, duration: 0.1)
|
|
245
|
+
CubicKeyframe(-10, duration: 0.1)
|
|
246
|
+
CubicKeyframe(0, duration: 0.1)
|
|
247
|
+
}
|
|
248
|
+
KeyframeTrack(\.scale) {
|
|
249
|
+
CubicKeyframe(1.1, duration: 0.25)
|
|
250
|
+
CubicKeyframe(1.0, duration: 0.25)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// BAD - manual timer-based animation
|
|
255
|
+
Image(systemName: "bell.fill")
|
|
256
|
+
.onTapGesture {
|
|
257
|
+
withAnimation(.easeOut(duration: 0.1)) { rotation = 15 }
|
|
258
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
259
|
+
withAnimation { rotation = -15 }
|
|
260
|
+
}
|
|
261
|
+
// ... more manual timing - error prone
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### KeyframeTimeline (iOS 17+)
|
|
266
|
+
|
|
267
|
+
Query animation values directly for testing or non-SwiftUI use.
|
|
268
|
+
|
|
269
|
+
```swift
|
|
270
|
+
let timeline = KeyframeTimeline(initialValue: AnimationValues()) {
|
|
271
|
+
KeyframeTrack(\.scale) {
|
|
272
|
+
CubicKeyframe(1.2, duration: 0.25)
|
|
273
|
+
CubicKeyframe(1.0, duration: 0.25)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let midpoint = timeline.value(time: 0.25)
|
|
278
|
+
print(midpoint.scale) // Value at 0.25 seconds
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Animation Completion Handlers (iOS 17+)
|
|
284
|
+
|
|
285
|
+
Execute code when animations finish.
|
|
286
|
+
|
|
287
|
+
### With withAnimation
|
|
288
|
+
|
|
289
|
+
```swift
|
|
290
|
+
// GOOD - completion with withAnimation
|
|
291
|
+
Button("Animate") {
|
|
292
|
+
withAnimation(.spring) {
|
|
293
|
+
isExpanded.toggle()
|
|
294
|
+
} completion: {
|
|
295
|
+
showNextStep = true
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### With Transaction (For Reexecution)
|
|
301
|
+
|
|
302
|
+
```swift
|
|
303
|
+
// GOOD - completion fires on every trigger change
|
|
304
|
+
Circle()
|
|
305
|
+
.scaleEffect(bounceCount % 2 == 0 ? 1.0 : 1.2)
|
|
306
|
+
.transaction(value: bounceCount) { transaction in
|
|
307
|
+
transaction.animation = .spring
|
|
308
|
+
transaction.addAnimationCompletion {
|
|
309
|
+
message = "Bounce \(bounceCount) complete"
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// BAD - completion only fires ONCE (no value parameter)
|
|
314
|
+
Circle()
|
|
315
|
+
.scaleEffect(bounceCount % 2 == 0 ? 1.0 : 1.2)
|
|
316
|
+
.animation(.spring, value: bounceCount)
|
|
317
|
+
.transaction { transaction in // No value!
|
|
318
|
+
transaction.addAnimationCompletion {
|
|
319
|
+
completionCount += 1 // Only fires once, ever
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## @Animatable Macro (iOS 26+)
|
|
327
|
+
|
|
328
|
+
The `@Animatable` macro auto-synthesizes `animatableData` from all animatable stored properties, eliminating verbose manual conformance. Use `@AnimatableIgnored` to exclude properties that should not animate.
|
|
329
|
+
|
|
330
|
+
### Before (Manual)
|
|
331
|
+
|
|
332
|
+
```swift
|
|
333
|
+
struct Wedge: Shape {
|
|
334
|
+
var startAngle: Angle
|
|
335
|
+
var endAngle: Angle
|
|
336
|
+
var drawClockwise: Bool
|
|
337
|
+
|
|
338
|
+
var animatableData: AnimatablePair<Double, Double> {
|
|
339
|
+
get { AnimatablePair(startAngle.radians, endAngle.radians) }
|
|
340
|
+
set {
|
|
341
|
+
startAngle = .radians(newValue.first)
|
|
342
|
+
endAngle = .radians(newValue.second)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
func path(in rect: CGRect) -> Path { /* ... */ }
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### After (@Animatable)
|
|
351
|
+
|
|
352
|
+
```swift
|
|
353
|
+
@Animatable
|
|
354
|
+
struct Wedge: Shape {
|
|
355
|
+
var startAngle: Angle
|
|
356
|
+
var endAngle: Angle
|
|
357
|
+
@AnimatableIgnored var drawClockwise: Bool
|
|
358
|
+
|
|
359
|
+
func path(in rect: CGRect) -> Path { /* ... */ }
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### When to Use
|
|
364
|
+
- **Prefer `@Animatable`** for any custom `Shape`, `AnimatableModifier`, or type conforming to `Animatable` with multiple properties
|
|
365
|
+
- **Use `@AnimatableIgnored`** for properties that control behavior but should not interpolate (e.g., directions, flags, identifiers)
|
|
366
|
+
- The macro works with any type conforming to `Animatable`, not just `Shape`
|
|
367
|
+
|
|
368
|
+
> Source: "What's new in SwiftUI" (WWDC25, session 256)
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Quick Reference
|
|
373
|
+
|
|
374
|
+
### Transactions (All iOS versions)
|
|
375
|
+
- `withTransaction` is the explicit form of `withAnimation`
|
|
376
|
+
- Implicit animations override explicit (later in view tree wins)
|
|
377
|
+
- Use `disablesAnimations` to prevent override
|
|
378
|
+
- Use `.transaction { $0.animation = nil }` to remove animation
|
|
379
|
+
|
|
380
|
+
### Custom Transaction Keys (iOS 17+)
|
|
381
|
+
- Pass metadata through animation system via `TransactionKey`
|
|
382
|
+
|
|
383
|
+
### Phase Animations (iOS 17+)
|
|
384
|
+
- Use for multi-step sequences returning to start
|
|
385
|
+
- Prefer enum phases for clarity
|
|
386
|
+
- Each phase change is a separate animation
|
|
387
|
+
- Use `trigger` parameter for one-shot animations
|
|
388
|
+
|
|
389
|
+
### Keyframe Animations (iOS 17+)
|
|
390
|
+
- Use for precise timing control
|
|
391
|
+
- Tracks run in parallel
|
|
392
|
+
- Use `KeyframeTimeline` for testing/advanced use
|
|
393
|
+
- Prefer over manual DispatchQueue timing
|
|
394
|
+
|
|
395
|
+
### Completion Handlers (iOS 17+)
|
|
396
|
+
- Use `withAnimation(.animation) { } completion: { }` for one-shot completion handlers
|
|
397
|
+
- Use `.transaction(value:)` for handlers that should refire on every value change
|
|
398
|
+
- Without `value:` parameter, completion only fires once
|
|
399
|
+
|
|
400
|
+
### @Animatable Macro (iOS 26+)
|
|
401
|
+
- Use `@Animatable` to auto-synthesize `animatableData` from stored properties
|
|
402
|
+
- Use `@AnimatableIgnored` to exclude non-animatable properties
|
|
403
|
+
- Replaces verbose manual `animatableData` getters/setters
|