@mmerterden/multi-agent-pipeline 20.17.1 → 20.18.0

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/docs/facts.json +6 -6
  3. package/install/_mcp-register.mjs +1 -1
  4. package/install/_xcode-skills.mjs +263 -0
  5. package/install/catalog-history.json +1 -1
  6. package/install/claude.mjs +23 -0
  7. package/install/codex.mjs +11 -0
  8. package/install/copilot.mjs +9 -0
  9. package/manifest.json +85 -45
  10. package/package.json +1 -1
  11. package/pipeline/commands/multi-agent/stack/SKILL.md +2 -0
  12. package/pipeline/lib/xcode-plugin.mjs +131 -0
  13. package/pipeline/multi-agent-refs/features/doctor.md +10 -0
  14. package/pipeline/scripts/doctor.mjs +38 -0
  15. package/pipeline/scripts/uninstall.mjs +57 -1
  16. package/pipeline/skills/.skill-manifest.json +33 -13
  17. package/pipeline/skills/.skills-index.json +66 -11
  18. package/pipeline/skills/shared/README.md +18 -13
  19. package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +3 -1
  20. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +2 -1
  21. package/pipeline/skills/shared/external/hig-app-icons/SKILL.md +159 -0
  22. package/pipeline/skills/shared/external/hig-app-icons/evals/evals.json +48 -0
  23. package/pipeline/skills/shared/external/hig-app-icons/evals/trigger-eval.json +22 -0
  24. package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +115 -0
  25. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +32 -4
  26. package/pipeline/skills/shared/external/hig-components-layout/evals/evals.json +47 -0
  27. package/pipeline/skills/shared/external/hig-components-layout/evals/trigger-eval.json +22 -0
  28. package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +231 -0
  29. package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +5 -30
  30. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
  31. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -4
  32. package/pipeline/skills/shared/external/hig-foundations/evals/evals.json +47 -0
  33. package/pipeline/skills/shared/external/hig-foundations/evals/trigger-eval.json +21 -0
  34. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +34 -26
  35. package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +219 -0
  36. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +5 -4
  37. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +19 -3
  38. package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +188 -0
  39. package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +5 -11
  40. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +17 -5
  41. package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +218 -0
  42. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +4 -1
  43. package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +135 -0
  44. package/pipeline/skills/shared/external/ios-design-review/SKILL.md +146 -0
  45. package/pipeline/skills/shared/external/ios-design-review/evals/evals.json +48 -0
  46. package/pipeline/skills/shared/external/ios-design-review/evals/trigger-eval.json +22 -0
  47. package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +135 -0
  48. package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +193 -0
  49. package/pipeline/skills/shared/external/ios-haptics/SKILL.md +191 -0
  50. package/pipeline/skills/shared/external/ios-haptics/evals/evals.json +49 -0
  51. package/pipeline/skills/shared/external/ios-haptics/evals/trigger-eval.json +22 -0
  52. package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +215 -0
  53. package/pipeline/skills/shared/external/ios-iphone-duo/NOTICE.md +53 -0
  54. package/pipeline/skills/shared/external/ios-iphone-duo/SKILL.md +179 -0
  55. package/pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json +65 -0
  56. package/pipeline/skills/shared/external/ios-iphone-duo/evals/trigger-eval.json +22 -0
  57. package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +212 -0
  58. package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +202 -0
  59. package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +122 -0
  60. package/pipeline/skills/shared/external/ios-sf-symbols/SKILL.md +177 -0
  61. package/pipeline/skills/shared/external/ios-sf-symbols/evals/evals.json +49 -0
  62. package/pipeline/skills/shared/external/ios-sf-symbols/evals/trigger-eval.json +22 -0
  63. package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +135 -0
  64. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +38 -50
  65. package/pipeline/skills/shared/external/swiftui-animation/evals/evals.json +47 -0
  66. package/pipeline/skills/shared/external/swiftui-animation/evals/trigger-eval.json +22 -0
  67. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +7 -73
  68. package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +185 -0
  69. package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +6 -6
  70. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +3 -253
  71. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +4 -47
  72. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +3 -111
  73. package/pipeline/skills/shared/external/swiftui-expert-skill/references/liquid-glass.md +11 -381
  74. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +184 -224
  75. package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/evals.json +47 -0
  76. package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/trigger-eval.json +22 -0
  77. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +246 -33
  78. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +227 -0
  79. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +15 -18
  80. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +1 -1
  81. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +6 -68
  82. package/pipeline/skills/skills-index.md +16 -11
  83. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +0 -786
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: ios-sf-symbols
3
+ description: "SF Symbols in SwiftUI and UIKit: rendering modes, variable values, symbol effects including Draw On and Off, matching weight and scale to text, custom symbols and accessibility labels. Use when picking, styling, animating or reviewing symbols, or building a custom symbol."
4
+ metadata:
5
+ source: multi-agent-pipeline
6
+ date_added: "2026-10-08"
7
+ ---
8
+
9
+ # SF Symbols
10
+
11
+ SF Symbols are vector glyphs drawn to sit with San Francisco text: nine
12
+ weights and three scales that line up with the font, layers that take colour,
13
+ and built-in animations. Using them well is mostly about letting them follow
14
+ the text and the container instead of sizing them by hand.
15
+
16
+ ## Versions and availability
17
+
18
+ Symbols and symbol features introduced in a given year are not available on
19
+ earlier systems, so check both the symbol's availability in the SF Symbols app
20
+ and the API's introduction version.
21
+
22
+ | Capability | API | iOS |
23
+ |---|---|---|
24
+ | Rendering modes | `symbolRenderingMode(_:)`, `SymbolRenderingMode` | 15 |
25
+ | Variants | `symbolVariant(_:)`, `SymbolVariants` | 15 |
26
+ | Variable value | `Image(systemName:variableValue:)` | 16 |
27
+ | Symbol effects | `symbolEffect(_:options:value:)`, `symbolEffect(_:options:isActive:)`, `contentTransition(.symbolEffect(...))` | 17 |
28
+ | Wiggle, Rotate, Breathe | `WiggleSymbolEffect`, `RotateSymbolEffect`, `BreatheSymbolEffect` | 18 |
29
+ | Draw On, Draw Off | `DrawOnSymbolEffect`, `DrawOffSymbolEffect` (`.drawOn`, `.drawOff`) | 26 |
30
+ | Variable Draw | `symbolVariableValueMode(_:)` with `.draw` | 26 |
31
+ | Gradient rendering | `symbolColorRenderingMode(_:)` with `.gradient` | 26 |
32
+
33
+ SF Symbols 7 introduced Draw, Variable Draw and gradients. The current SF
34
+ Symbols app (27 releases) adds semantic search and new symbols. Check
35
+ developer.apple.com/sf-symbols for the current app before relying on a symbol
36
+ that may be new.
37
+
38
+ ## Sizing with text
39
+
40
+ Symbols take their size and weight from the font, which is what keeps them
41
+ aligned and scaling with Dynamic Type.
42
+
43
+ ```swift
44
+ Label("Downloads", systemImage: "arrow.down.circle")
45
+ .font(.headline)
46
+
47
+ Image(systemName: "bell")
48
+ .font(.body)
49
+ .imageScale(.large)
50
+ ```
51
+
52
+ - Set size with `.font(...)` and a text style, not `.resizable().frame(...)`.
53
+ A resized image stops scaling with Dynamic Type and loses weight matching.
54
+ - Use `imageScale(_:)` (small, medium, large) to change emphasis relative to
55
+ the text without breaking the weight match.
56
+ - Match weight to adjacent text (`.fontWeight(.semibold)` on both).
57
+ - In UIKit, `UIImage.SymbolConfiguration(textStyle:)` or
58
+ `init(font:scale:)` does the same job.
59
+
60
+ ## Rendering modes
61
+
62
+ | Mode | What it does | Good for |
63
+ |---|---|---|
64
+ | Monochrome | One colour for every layer | Toolbars, lists, most UI |
65
+ | Hierarchical | One colour, layers at decreasing opacity | Depth without extra colours |
66
+ | Palette | One explicit colour per layer | Brand or status colour pairs |
67
+ | Multicolor | The symbol's intrinsic colours | Weather, files, recognisable objects |
68
+
69
+ ```swift
70
+ Image(systemName: "cloud.sun.rain.fill")
71
+ .symbolRenderingMode(.hierarchical)
72
+ .foregroundStyle(.tint)
73
+
74
+ Image(systemName: "person.crop.circle.badge.checkmark")
75
+ .symbolRenderingMode(.palette)
76
+ .foregroundStyle(.green, .secondary)
77
+ ```
78
+
79
+ Gradient rendering (iOS 26) adds a light linear gradient from a single colour
80
+ in any mode: `.symbolColorRenderingMode(.gradient)`. It looks best at larger
81
+ sizes. Prefer system colours in every mode so symbols adapt to dark mode,
82
+ vibrancy and Increase Contrast. Check the automatic choice at small sizes and
83
+ on busy backgrounds, where a different mode may read better.
84
+
85
+ ## Variants
86
+
87
+ Outline is the default and sits best next to text. Fill gives emphasis and is
88
+ what tab bars prefer; slash marks something unavailable; enclosed shapes help
89
+ legibility at small sizes. Containers often pick the variant (a tab bar
90
+ chooses fill), so set `symbolVariant(_:)` only when the context does not.
91
+
92
+ ## Variable values
93
+
94
+ A variable value (0 to 1) shows a level: signal, volume, progress.
95
+
96
+ ```swift
97
+ Image(systemName: "wifi", variableValue: strength)
98
+
99
+ Image(systemName: "thermometer.high", variableValue: level)
100
+ .symbolVariableValueMode(.draw)
101
+ ```
102
+
103
+ Variable Color changes layer opacity at thresholds; Variable Draw (iOS 26)
104
+ draws the path to the value, which reads more precisely. Use variable values
105
+ to show change, not depth; hierarchical rendering is for depth.
106
+
107
+ ## Effects
108
+
109
+ Effects tell people that something happened or is happening. Each has a
110
+ meaning; pick by meaning, not by look.
111
+
112
+ | Effect | Use | Trigger |
113
+ |---|---|---|
114
+ | Bounce | An action happened | `value:` changes |
115
+ | Pulse, Breathe | Ongoing activity | `isActive:` |
116
+ | Variable Color | Progress or connecting | `isActive:` |
117
+ | Wiggle | Draw attention to something easy to miss | `value:` |
118
+ | Rotate | Work in progress, rotating parts | `isActive:` |
119
+ | Scale | Persistent emphasis on a selection | `isActive:` |
120
+ | Appear, Disappear | Show or hide a symbol | `isActive:` or transition |
121
+ | Draw On, Draw Off | Draw in or out along the path (iOS 26) | `isActive:` or transition |
122
+ | Replace, Magic Replace (18) | Swap one symbol for a related one | `contentTransition` |
123
+
124
+ ```swift
125
+ Image(systemName: "bell")
126
+ .symbolEffect(.bounce, value: unreadCount)
127
+
128
+ Image(systemName: isMuted ? "speaker.slash" : "speaker.wave.2")
129
+ .contentTransition(.symbolEffect(.replace))
130
+
131
+ Image(systemName: "checkmark.circle")
132
+ .symbolEffect(.drawOff.byLayer, isActive: !isSaved)
133
+ ```
134
+
135
+ Use effects sparingly; several animated symbols on one screen compete. Repeat
136
+ indefinitely only while the condition holds. Respect Reduce Motion: read
137
+ `accessibilityReduceMotion` and drop decorative repeating effects when it is
138
+ on, keeping the state change itself visible. Full effect options and the
139
+ UIKit equivalents are in `references/effects-and-custom-symbols.md`.
140
+
141
+ ## Custom symbols
142
+
143
+ When no system symbol fits, build one from the SF Symbols template rather than
144
+ drawing an icon from scratch, so it gets weights, scales, rendering modes and
145
+ animations. In short: export the closest system symbol's template from the SF
146
+ Symbols app, edit it in a vector tool, import it back, annotate layers (and
147
+ Draw guide points if it animates), test every effect, export, and add it to an
148
+ asset catalog as a symbol image. Load it with `Image("name")` or
149
+ `UIImage(named:)`. The full process is in the reference.
150
+
151
+ Symbols that depict Apple products or features cannot be customised; the SF
152
+ Symbols app marks them with an Info badge.
153
+
154
+ ## Accessibility
155
+
156
+ - Icon-only controls need a label. `Label("Share", systemImage: ...)` gives
157
+ VoiceOver and overflow menus a title even when only the icon shows.
158
+ - Use `.accessibilityLabel(...)` when the label must differ from visible text,
159
+ and hide purely decorative symbols with `.accessibilityHidden(true)`.
160
+ - Custom symbols need an accessibility description; VoiceOver cannot infer one.
161
+ - Directional symbols have right-to-left variants; custom symbols need their
162
+ directionality set when exported.
163
+
164
+ ## Not in app icons
165
+
166
+ The SF Symbols licence prohibits using symbols, or confusingly similar images,
167
+ in app icons, logos or any trademarked use. Draw the icon's artwork
168
+ separately; see `hig-app-icons`.
169
+
170
+ ## References
171
+
172
+ - `references/effects-and-custom-symbols.md`: effect options, UIKit API, custom symbol workflow, review checklist.
173
+ - HIG SF Symbols: https://developer.apple.com/design/human-interface-guidelines/sf-symbols
174
+ - SF Symbols app: https://developer.apple.com/sf-symbols/
175
+ - Symbols framework: https://developer.apple.com/documentation/symbols
176
+ - Configuring and displaying symbol images: https://developer.apple.com/documentation/uikit/configuring-and-displaying-symbol-images-in-your-ui
177
+ - Creating custom symbols: https://developer.apple.com/documentation/technologyoverviews/custom-sf-symbols
@@ -0,0 +1,49 @@
1
+ {
2
+ "skill_name": "ios-sf-symbols",
3
+ "evals": [
4
+ {
5
+ "id": 0,
6
+ "name": "symbol-misaligned-with-text",
7
+ "prompt": "In our settings rows the SF Symbol looks too thin and doesn't grow when users pick a larger text size. The code is Image(systemName: \"bell\").resizable().frame(width: 18, height: 18) next to Text(\"Notifications\").font(.body).fontWeight(.semibold). How should it be done?",
8
+ "expected_output": "Removes resizable and the fixed frame and sizes the symbol through the font (text style) so it scales with Dynamic Type and keeps the weight match; matches the semibold weight; uses imageScale for emphasis; suggests Label so the row has a title for accessibility.",
9
+ "files": [],
10
+ "expectations": [
11
+ "Removes resizable plus fixed frame sizing",
12
+ "Sizes the symbol with a font or text style so it follows Dynamic Type",
13
+ "Matches the symbol weight to the adjacent semibold text",
14
+ "Mentions imageScale for relative emphasis",
15
+ "Suggests Label or an accessibility label"
16
+ ]
17
+ },
18
+ {
19
+ "id": 1,
20
+ "name": "status-and-animation-choices",
21
+ "prompt": "We show Wi-Fi signal strength, a syncing state, and a 'saved' confirmation with SF Symbols in an iOS 26 app. Which rendering modes and symbol effects should we use for each, with SwiftUI code, and anything to watch for accessibility?",
22
+ "expected_output": "Signal strength with a variable value (Image(systemName:variableValue:)), optionally Variable Draw via symbolVariableValueMode(.draw) on iOS 26. Syncing as an indefinite effect (pulse, breathe, rotate or variable color) driven by isActive and stopped when sync ends. Saved confirmation as a discrete bounce on a value change or Draw On or Off (iOS 26). Recommends hierarchical or monochrome with system colours, and gating decorative repeating effects on accessibilityReduceMotion.",
23
+ "files": [],
24
+ "expectations": [
25
+ "Uses a variable value for signal strength",
26
+ "Mentions Variable Draw or symbolVariableValueMode for iOS 26",
27
+ "Drives the syncing effect with isActive and stops it when done",
28
+ "Uses a discrete effect (bounce) or Draw for the saved confirmation",
29
+ "Respects Reduce Motion for repeating effects",
30
+ "Recommends system colours or an appropriate rendering mode"
31
+ ]
32
+ },
33
+ {
34
+ "id": 2,
35
+ "name": "custom-symbol-and-icon",
36
+ "prompt": "We need a custom 'boarding pass' symbol that animates like system ones, and marketing also wants to reuse it as the app icon. Walk us through it.",
37
+ "expected_output": "Duplicate a close system symbol as a custom symbol, export a variable template (Ultralight-S, Regular-S, Black-S), keep matching path counts, convert strokes to fills, no hidden paths or effects, align to guides, export SVG with high precision, validate, import, annotate layers and Draw guide points, test all animation presets, add an accessibility description and directionality, add to an asset catalog and load by name. Refuses the app icon reuse because the SF Symbols terms prohibit symbols or look-alikes in app icons and logos, and routes icon work to hig-app-icons.",
38
+ "files": [],
39
+ "expectations": [
40
+ "Starts from Duplicate as Custom Symbol and Export Template",
41
+ "Explains the variable template with three interpolation sources",
42
+ "Requires matching paths, filled shapes and validation",
43
+ "Mentions annotation including Draw guide points and testing animations",
44
+ "Mentions an accessibility description for the custom symbol",
45
+ "Says symbols may not be used as the app icon and points to hig-app-icons"
46
+ ]
47
+ }
48
+ ]
49
+ }
@@ -0,0 +1,22 @@
1
+ [
2
+ { "query": "Which SF Symbols rendering mode should I use for a multicolor weather icon?", "should_trigger": true },
3
+ { "query": "Animate the bell symbol when a new notification arrives", "should_trigger": true },
4
+ { "query": "How do I use Draw On and Draw Off symbol effects on iOS 26?", "should_trigger": true },
5
+ { "query": "Make the Wi-Fi symbol show signal strength with a variable value", "should_trigger": true },
6
+ { "query": "Create a custom SF Symbol from a template and annotate its layers", "should_trigger": true },
7
+ { "query": "Our symbol looks thinner than the text next to it", "should_trigger": true },
8
+ { "query": "Use palette rendering with two colors on a badge symbol", "should_trigger": true },
9
+ { "query": "Swap the play symbol for pause with a smooth transition", "should_trigger": true },
10
+ { "query": "UIImage.SymbolConfiguration for a large headline symbol in UIKit", "should_trigger": true },
11
+ { "query": "What's new in SF Symbols 7 and 8 for gradients?", "should_trigger": true },
12
+ { "query": "Design our app icon in Icon Composer", "should_trigger": false },
13
+ { "query": "Add a Liquid Glass background to the floating button", "should_trigger": false },
14
+ { "query": "Pick a custom font that supports Dynamic Type", "should_trigger": false },
15
+ { "query": "Play a success haptic when the save completes", "should_trigger": false },
16
+ { "query": "Add a spring animation to the card when it expands", "should_trigger": false },
17
+ { "query": "Load remote images asynchronously in a list", "should_trigger": false },
18
+ { "query": "Design review of the whole settings screen in dark mode", "should_trigger": false },
19
+ { "query": "Add VoiceOver rotor support to our custom chart", "should_trigger": false },
20
+ { "query": "Export vector PDF assets for the onboarding illustrations", "should_trigger": false },
21
+ { "query": "Build a tab bar with five destinations", "should_trigger": false }
22
+ ]
@@ -0,0 +1,135 @@
1
+ # Symbol effects, UIKit API and custom symbols
2
+
3
+ ## Contents
4
+
5
+ 1. Effect options
6
+ 2. Effects in UIKit
7
+ 3. Rendering and configuration in UIKit
8
+ 4. Custom symbol workflow
9
+ 5. Review checklist
10
+
11
+ ## 1. Effect options
12
+
13
+ Effects live in the Symbols framework (`SymbolEffect` and its conforming
14
+ types) and are applied with SwiftUI modifiers or UIKit image view methods.
15
+
16
+ **How an effect is driven** decides the modifier:
17
+
18
+ - Discrete effects (Bounce, Wiggle) play once each time a value changes:
19
+ `.symbolEffect(.bounce, value: count)`.
20
+ - Indefinite effects (Pulse, Breathe, Variable Color, Rotate, Scale) run
21
+ while a condition is true: `.symbolEffect(.pulse, isActive: isSyncing)`.
22
+ - Transition effects (Appear, Disappear, Draw On, Draw Off) animate a symbol
23
+ in or out: as `isActive` effects, or as a view transition with
24
+ `.transition(.symbolEffect(.drawOn))`.
25
+ - Content transitions (Replace, Magic Replace) animate one symbol becoming
26
+ another: `.contentTransition(.symbolEffect(.replace))` on an image whose
27
+ `systemName` changes.
28
+
29
+ **Options** (`SymbolEffectOptions`): `.repeating`, `.nonRepeating`,
30
+ `.repeat(_:)` for a count or behaviour, and `.speed(_:)`.
31
+
32
+ ```swift
33
+ Image(systemName: "antenna.radiowaves.left.and.right")
34
+ .symbolEffect(.variableColor.iterative, options: .repeating, isActive: isConnecting)
35
+ ```
36
+
37
+ **Layer playback.** Many effects can run on the whole symbol or layer by
38
+ layer (`.byLayer`, `.wholeSymbol`). Draw On and Draw Off add `.individually`,
39
+ which draws one layer at a time for a deliberate reveal.
40
+
41
+ **Replace directions.** `.replace.downUp` (state change), `.replace.upUp`
42
+ (forward progression), `.replace.offUp` (emphasise the next state). Magic
43
+ Replace animates related shapes (a slash drawing on, a badge appearing) and
44
+ falls back to the default replace for unrelated symbols; `.replace.magic(fallback:)` (iOS 18)
45
+ chooses the fallback.
46
+
47
+ **Removing inherited effects.** `symbolEffectsRemoved(_:)` stops effects
48
+ applied higher in the hierarchy from reaching a subview.
49
+
50
+ **Choosing well.** Bounce says "this just happened". Pulse and Breathe say
51
+ "this is ongoing" (Breathe also changes size). Wiggle points at something easy
52
+ to miss. Rotate shows work in progress. Scale holds attention on a selection
53
+ until removed. Draw suits progress and directional meaning. One effect per
54
+ message, and stop indefinite effects when the condition ends.
55
+
56
+ **Reduce Motion.** Read `@Environment(\.accessibilityReduceMotion)` and skip
57
+ decorative or repeating effects when it is true. Keep the outcome visible
58
+ (the new symbol, the changed state) without the movement.
59
+
60
+ ## 2. Effects in UIKit
61
+
62
+ `UIImageView` carries the same effects:
63
+
64
+ ```swift
65
+ imageView.addSymbolEffect(.bounce)
66
+ imageView.addSymbolEffect(.pulse, options: .repeating)
67
+ imageView.removeSymbolEffect(ofType: .pulse)
68
+ if let muted = UIImage(systemName: "speaker.slash") {
69
+ imageView.setSymbolImage(muted, contentTransition: .replace)
70
+ }
71
+ ```
72
+
73
+ `removeAllSymbolEffects()` clears everything; completion handlers report when
74
+ a discrete effect finishes.
75
+
76
+ ## 3. Rendering and configuration in UIKit
77
+
78
+ `UIImage.SymbolConfiguration` composes with `applying(_:)`:
79
+
80
+ ```swift
81
+ let size = UIImage.SymbolConfiguration(textStyle: .headline, scale: .large)
82
+ let palette = UIImage.SymbolConfiguration(paletteColors: [.systemGreen, .secondaryLabel])
83
+ let image = UIImage(systemName: "person.crop.circle.badge.checkmark",
84
+ withConfiguration: size.applying(palette))
85
+ ```
86
+
87
+ Other initialisers and modifiers: `init(hierarchicalColor:)`,
88
+ `preferringMulticolor()`, `preferringMonochrome()`, `init(weight:)`,
89
+ `init(pointSize:weight:scale:)`, and on iOS 26 `init(colorRenderingMode:)`
90
+ (gradient) and `init(variableValueMode:)` (draw). Set a view-level default
91
+ with `UIImageView.preferredSymbolConfiguration`.
92
+
93
+ ## 4. Custom symbol workflow
94
+
95
+ 1. In the SF Symbols app, select the closest system symbol and choose File >
96
+ Duplicate as Custom Symbol, then File > Export Template.
97
+ 2. Pick a template: **variable** (three sources, `Ultralight-S`, `Regular-S`,
98
+ `Black-S`, from which the other weights and scales are interpolated) or
99
+ **static** (up to 27 explicit variants for a specific weight and scale).
100
+ 3. Edit in a vector tool. Keep the same number of paths and control points
101
+ across the three sources so interpolation works. Convert strokes to filled
102
+ paths, use flat fills with no effects, close every shape, and do not leave
103
+ hidden paths (they are picked up as outlines). Keep the `template-version`
104
+ layer and lock the artboard.
105
+ 4. Align with the template guides (baseline, cap height, margins) so the
106
+ symbol sits with text. Negative side margins are allowed for optical
107
+ alignment, named like `left-margin-Regular-M`.
108
+ 5. Export SVG at 7 or more decimal places, then validate with File > Validate
109
+ Templates or by adding it to an asset catalog.
110
+ 6. Import back into the SF Symbols app and annotate: layer colours or
111
+ hierarchy levels for the rendering modes, variable-colour order, and for
112
+ Draw animations the guide points on each layer (placed for Ultralight,
113
+ Regular and Black and translated across weights).
114
+ 7. Test every animation preset; shapes in motion can show seams that the
115
+ static symbol hides. Drawing whole shapes plus an erase layer, instead of
116
+ cut-outs, keeps layer information that animations rely on.
117
+ 8. Use the component library for badges and enclosures instead of drawing
118
+ them into the symbol.
119
+ 9. Set directionality for right-to-left and give the symbol an accessibility
120
+ description.
121
+ 10. Export and add to the asset catalog as a symbol image; load with
122
+ `Image("name")` or `UIImage(named:)`.
123
+
124
+ ## 5. Review checklist
125
+
126
+ - [ ] Sized by font and text style, not by frame; scales with Dynamic Type.
127
+ - [ ] Weight matches adjacent text; scale used for emphasis.
128
+ - [ ] Consistent variant per meaning; fill only where the container wants it.
129
+ - [ ] Rendering mode legible at the size used, in light, dark and Increase Contrast.
130
+ - [ ] Variable values used for change, hierarchical for depth.
131
+ - [ ] Each effect has one clear meaning; indefinite effects stop with their condition.
132
+ - [ ] Decorative motion skipped under Reduce Motion.
133
+ - [ ] Icon-only controls labelled; decorative symbols hidden from VoiceOver.
134
+ - [ ] Symbol and API available on the deployment target.
135
+ - [ ] No symbols in the app icon or logo.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: swiftui-animation
3
- description: "SwiftUI motion: withAnimation, scoped implicit .animation(_:body:) and .animation(_:value:), timing curves and springs (.smooth, .snappy, .bouncy, Spring), PhaseAnimator, KeyframeAnimator, @Animatable, matchedGeometryEffect and matchedTransitionSource zoom transitions, insertion and content transitions, SF Symbol effects (pulse, bounce, wiggle, rotate, breathe, variableColor), custom Transition and CustomAnimation, performance, Core Animation bridging. Use when implementing, reviewing or improving SwiftUI animations and transitions, or making them respect accessibilityReduceMotion."
3
+ description: "SwiftUI motion: withAnimation, springs, PhaseAnimator, KeyframeAnimator, matched geometry and zoom transitions, scrollTransition and visualEffect, custom transitions, Reduce Motion. Use when implementing, reviewing or improving SwiftUI animations, transitions or scroll-linked effects."
4
4
  metadata:
5
5
  source: multi-agent-pipeline
6
6
  ---
@@ -24,7 +24,9 @@ release an API first shipped in.
24
24
  | A push or pop in `NavigationStack` that grows from a tile | `matchedTransitionSource` + `.navigationTransition(.zoom)` |
25
25
  | A view being inserted or removed | `.transition()` |
26
26
  | Text, numbers or a symbol changing in place | `.contentTransition()` |
27
- | An SF Symbol reacting | `.symbolEffect()` |
27
+ | An SF Symbol reacting | symbol effects: `ios-sf-symbols` |
28
+ | Items reacting to their position in a scroll view | `.scrollTransition` |
29
+ | An effect driven by the view's own geometry (parallax) | `.visualEffect` |
28
30
  | A timing curve no built-in covers | `CustomAnimation` |
29
31
 
30
32
  ### 2. Pick the curve
@@ -312,60 +314,39 @@ Image(systemName: isMuted ? "speaker.slash.fill" : "speaker.wave.2.fill")
312
314
  Kinds: `.identity`, `.interpolate`, `.opacity`, `.numericText(countsDown:)`,
313
315
  `.numericText(value:)` (iOS 17), `.symbolEffect` (iOS 17).
314
316
 
315
- ## Symbol effects (iOS 17+)
317
+ Symbol effects (`.symbolEffect`, its options and Reduce Motion handling): `ios-sf-symbols`.
316
318
 
317
- Discrete effects play once when a value changes; indefinite ones run while a
318
- condition is true.
319
+ ## Scroll-driven effects (iOS 17+)
319
320
 
320
- ```swift
321
- Image(systemName: "envelope").symbolEffect(.bounce, value: unreadCount)
322
- Image(systemName: "bell").symbolEffect(.wiggle.clockwise, value: alertCount) // iOS 18
323
- Image(systemName: "mic.fill").symbolEffect(.pulse, isActive: isListening)
324
- Image(systemName: "lungs.fill").symbolEffect(.breathe, isActive: isResting) // iOS 18
325
- Image(systemName: "wifi")
326
- .symbolEffect(.variableColor.iterative.reversing.dimInactiveLayers,
327
- options: .repeating, isActive: isSearching)
328
- ```
329
-
330
- Effects: `.appear`, `.disappear`, `.replace`, `.scale`, `.pulse`, `.bounce`
331
- and `.variableColor`; iOS 18 adds `.wiggle`, `.rotate` and `.breathe`.
332
- Most accept a scope, `.byLayer` or `.wholeSymbol`; which directions are
333
- available depends on the effect.
334
-
335
- ### Rendering modes
336
-
337
- `.symbolRenderingMode(_:)` decides how a symbol's layers are coloured.
338
-
339
- | Mode | Result | Good for |
340
- |---|---|---|
341
- | `.monochrome` (default) | one colour everywhere | toolbar icons that match text |
342
- | `.hierarchical` | one colour, layers at stepped opacity | depth without extra colours |
343
- | `.multicolor` | the symbol's own fixed colours | weather, file types, the intended palette |
344
- | `.palette` | your colours per layer via `.foregroundStyle(_:_:)` | brand or custom multi-colour icons |
321
+ `.scrollTransition` changes a view as it enters and leaves the visible part of
322
+ a scroll view; `.visualEffect` applies effects computed from the view's
323
+ geometry. Neither changes layout, so both are cheap per frame.
345
324
 
346
325
  ```swift
347
- Image(systemName: "folder.fill.badge.gearshape")
348
- .symbolRenderingMode(.hierarchical)
349
- .foregroundStyle(.teal)
350
-
351
- Image(systemName: "person.2.badge.plus.fill")
352
- .symbolRenderingMode(.palette)
353
- .foregroundStyle(.indigo, .orange)
354
-
355
- Image(systemName: "cloud.bolt.rain.fill")
356
- .symbolRenderingMode(.multicolor)
326
+ ForEach(destinations) { destination in
327
+ DestinationCard(destination: destination)
328
+ .scrollTransition { content, phase in
329
+ content
330
+ .opacity(phase.isIdentity ? 1 : 0.4)
331
+ .scaleEffect(phase.isIdentity ? 1 : 0.92)
332
+ }
333
+ }
357
334
 
358
- Image(systemName: "cellularbars", variableValue: signalLevel)
335
+ HeroImage()
336
+ .visualEffect { content, proxy in
337
+ content.offset(y: max(0, proxy.frame(in: .scrollView).minY) / -2)
338
+ }
359
339
  ```
360
340
 
361
- Symbols with variable colour show a level, such as signal strength or volume,
362
- from a value between 0.0 and 1.0. The value is passed to the image itself
363
- through `Image(systemName:variableValue:)`; there is no separate modifier for
364
- it.
341
+ - `phase.value` runs from -1 (top or leading edge) through 0 (visible) to 1
342
+ (bottom or trailing edge). Leave the view unchanged at `.identity`.
343
+ - The closures are `@Sendable`: capture values before them rather than reading
344
+ view state inside.
345
+ - Under Reduce Motion keep opacity, drop movement and parallax, or pass
346
+ `.identity` as the configuration.
365
347
 
366
- Apple docs:
367
- [SymbolRenderingMode](https://developer.apple.com/documentation/swiftui/symbolrenderingmode),
368
- [symbolRenderingMode(_:)](https://developer.apple.com/documentation/swiftui/view/symbolrenderingmode(_:)).
348
+ Configurations, thresholds, the effects available and parallax recipes:
349
+ [references/scroll-effects.md](references/scroll-effects.md).
369
350
 
370
351
  ## Common mistakes
371
352
 
@@ -384,6 +365,8 @@ Apple docs:
384
365
  `.animation(_:value:)`.
385
366
  7. **`.navigationTransition(.zoom(sourceID:in:))` on an inner container.** Put
386
367
  it on the outermost destination view.
368
+ 8. **Parallax from `GeometryReader` and `@State` offsets.** Use
369
+ `.visualEffect`, which does not invalidate the view on every scroll tick.
387
370
 
388
371
  ## Review checklist
389
372
 
@@ -397,6 +380,8 @@ Apple docs:
397
380
  - [ ] `@Animatable` used instead of hand-written `animatableData`
398
381
  - [ ] `accessibilityReduceMotion` respected; no `DispatchQueue` timing, no
399
382
  `UIView.animate`
383
+ - [ ] Scroll-linked effects use `.scrollTransition` or `.visualEffect`, leave
384
+ the identity phase untouched and drop movement under Reduce Motion
400
385
  - [ ] Insertions and removals use `.transition()`; each `contentTransition`
401
386
  has an animation, with the narrowest implicit scope that works
402
387
  - [ ] State that drives animation changes on `@MainActor`, and the types
@@ -407,7 +392,10 @@ Apple docs:
407
392
  - [Advanced animation](references/animation-advanced.md): `CustomAnimation`,
408
393
  every `Spring` initializer, `UnitCurve`, deeper `PhaseAnimator` and
409
394
  `KeyframeAnimator` patterns, `Transaction` and `TransactionKey`, all
410
- transition types including custom ones, every symbol effect option, Reduce
411
- Motion patterns, performance.
395
+ transition types including custom ones, Reduce Motion patterns,
396
+ performance.
397
+ - [Scroll effects](references/scroll-effects.md): `scrollTransition` phases,
398
+ configurations and thresholds, `visualEffect` with `GeometryProxy`,
399
+ parallax, Sendable closures, Reduce Motion.
412
400
  - [Core Animation bridge](references/core-animation-bridge.md): when to drop to
413
401
  `CALayer` animation and how to host it in SwiftUI.
@@ -0,0 +1,47 @@
1
+ {
2
+ "skill_name": "swiftui-animation",
3
+ "evals": [
4
+ {
5
+ "id": 0,
6
+ "name": "carousel-scroll-transition",
7
+ "prompt": "Horizontal destination carousel in SwiftUI (iOS 17+). Right now we use a GeometryReader in every card plus an @State offset dictionary to scale and tilt cards toward the edges, and scrolling stutters on older iPhones. Rewrite it properly, and make sure it is fine for people who turn on Reduce Motion.",
8
+ "expected_output": "Replaces the GeometryReader and state with .scrollTransition(axis: .horizontal) using ScrollTransitionPhase: scale and rotation driven by phase.value or isIdentity, no change in the identity phase, effects limited to visual effects. Optionally uses .scrollTargetBehavior(.viewAligned) with scrollTargetLayout. Reads accessibilityReduceMotion and keeps only opacity or passes .identity configuration, dropping rotation and scale. Notes the closure is Sendable so values are captured outside.",
9
+ "files": [],
10
+ "expectations": [
11
+ "Uses .scrollTransition instead of GeometryReader plus @State offsets",
12
+ "Drives the effect from ScrollTransitionPhase (value or isIdentity) and leaves the identity phase unchanged",
13
+ "Uses the horizontal axis for the carousel",
14
+ "Respects accessibilityReduceMotion by dropping rotation/scale or using the .identity configuration",
15
+ "Explains why it is cheaper: visual effects do not change layout or invalidate state every frame"
16
+ ]
17
+ },
18
+ {
19
+ "id": 1,
20
+ "name": "parallax-header",
21
+ "prompt": "I want the classic stretchy parallax header image at the top of a ScrollView in SwiftUI: when you pull down it stretches, when you scroll up it moves at half speed. iOS 17 minimum. What's the modern way?",
22
+ "expected_output": "Uses .visualEffect with the GeometryProxy, reading proxy.frame(in: .scrollView).minY, applying offset for half-speed movement and scale for the stretch, without GeometryReader-driven @State. Explains the closure is @Sendable and only visual effects (offset, scale, blur, opacity) are available, not layout. Mentions disabling parallax under Reduce Motion.",
23
+ "files": [],
24
+ "expectations": [
25
+ "Uses .visualEffect with the GeometryProxy",
26
+ "Reads the position in the scroll view coordinate space (.scrollView)",
27
+ "Applies offset and scale effects rather than changing frame or padding",
28
+ "Mentions turning parallax off when accessibilityReduceMotion is on",
29
+ "Does not rely on GeometryReader plus @State updates on every scroll"
30
+ ]
31
+ },
32
+ {
33
+ "id": 2,
34
+ "name": "button-feedback-curve",
35
+ "prompt": "Code review: Button { withAnimation(.easeInOut(duration: 1.0)) { isLiked.toggle() } } label: { Image(systemName: isLiked ? \"heart.fill\" : \"heart\").scaleEffect(isLiked ? 1.3 : 1).animation(.linear) }. Also there's a DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { withAnimation { showToast = true } } after it. Fix it.",
36
+ "expected_output": "Replaces the slow ease and bare .animation(.linear) with a spring (.snappy or .bouncy) via withAnimation or .animation(_:value:) or .animation(_:body:), uses .contentTransition(.symbolEffect(.replace)) or a symbol effect for the heart swap, replaces DispatchQueue delay with Animation.delay, and respects Reduce Motion.",
37
+ "files": [],
38
+ "expectations": [
39
+ "Removes the bare .animation(.linear) without a value",
40
+ "Uses a spring curve such as .snappy or .bouncy for the tap feedback",
41
+ "Uses a symbol content transition or symbol effect for the heart change",
42
+ "Replaces DispatchQueue.main.asyncAfter with Animation.delay or an equivalent animation-based delay",
43
+ "Mentions accessibilityReduceMotion"
44
+ ]
45
+ }
46
+ ]
47
+ }
@@ -0,0 +1,22 @@
1
+ [
2
+ {"query": "cards in my horizontal ScrollView should shrink and fade as they move toward the edges, swiftui", "should_trigger": true},
3
+ {"query": "stretchy parallax header image in SwiftUI without GeometryReader hacks", "should_trigger": true},
4
+ {"query": "my heart button animation feels sluggish, what spring should a tap use", "should_trigger": true},
5
+ {"query": "PhaseAnimator vs KeyframeAnimator for a bouncing notification bell", "should_trigger": true},
6
+ {"query": "zoom transition from a grid thumbnail into the detail screen in NavigationStack", "should_trigger": true},
7
+ {"query": "the number label should roll when the count changes, like a ticker", "should_trigger": true},
8
+ {"query": "how do I make all these scroll animations behave when the user enabled Reduce Motion", "should_trigger": true},
9
+ {"query": "visualEffect closure gives a Sendable error when I read my @State inside it", "should_trigger": true},
10
+ {"query": "rows should blur slightly when they're about to scroll off the top", "should_trigger": true},
11
+ {"query": "custom Transition type for a card flying in from the bottom with rotation", "should_trigger": true},
12
+ {"query": "glass buttons should morph into each other when the menu expands on iOS 26", "should_trigger": false},
13
+ {"query": "tab bar should minimize when scrolling down in iOS 26", "should_trigger": false},
14
+ {"query": "which SF Symbol rendering mode should I use for a multicolor weather icon", "should_trigger": false},
15
+ {"query": "add haptic feedback when the like button is tapped", "should_trigger": false},
16
+ {"query": "scroll to a specific message id programmatically in a chat list", "should_trigger": false},
17
+ {"query": "our list scrolls slowly with 5000 rows, how do I profile SwiftUI body recomputation", "should_trigger": false},
18
+ {"query": "Lottie vs Rive for an onboarding illustration", "should_trigger": false},
19
+ {"query": "Jetpack Compose animateFloatAsState spring parameters", "should_trigger": false},
20
+ {"query": "CSS scroll-driven animations with animation-timeline", "should_trigger": false},
21
+ {"query": "pull to refresh in a SwiftUI List", "should_trigger": false}
22
+ ]