Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
7
.claude/skills/axiom-sf-symbols/.openskills.json
Normal file
7
.claude/skills/axiom-sf-symbols/.openskills.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"source": "CharlesWiltgen/Axiom",
|
||||
"sourceType": "git",
|
||||
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
|
||||
"subpath": "axiom-codex/skills/axiom-sf-symbols",
|
||||
"installedAt": "2026-04-12T08:06:36.899Z"
|
||||
}
|
||||
593
.claude/skills/axiom-sf-symbols/SKILL.md
Normal file
593
.claude/skills/axiom-sf-symbols/SKILL.md
Normal file
@@ -0,0 +1,593 @@
|
||||
---
|
||||
name: axiom-sf-symbols
|
||||
description: Use when implementing SF Symbols rendering modes, symbol effects, animations, custom symbols, or troubleshooting symbol appearance - covers the full symbol effects system from iOS 17 through SF Symbols 7 Draw animations in iOS 26
|
||||
license: MIT
|
||||
compatibility: iOS 17+, iOS 18+ (Wiggle/Rotate/Breathe), iOS 26+ (Draw animations)
|
||||
metadata:
|
||||
version: "1.0.0"
|
||||
---
|
||||
|
||||
# SF Symbols — Effects, Rendering, and Custom Symbols
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use when:
|
||||
- Choosing between rendering modes (Monochrome, Hierarchical, Palette, Multicolor)
|
||||
- Implementing symbol effects or animations (Bounce, Pulse, Scale, Wiggle, Rotate, Breathe, Draw)
|
||||
- Working with SF Symbols 7 Draw On/Off animations
|
||||
- Creating custom symbols in the SF Symbols app
|
||||
- Troubleshooting symbol colors, effects not playing, or weight mismatches
|
||||
- Deciding which effect matches a specific UX purpose
|
||||
- Handling accessibility with symbol animations (Reduce Motion)
|
||||
|
||||
#### Related Skills
|
||||
- Use `axiom-sf-symbols-ref` for complete API reference with all modifiers, UIKit equivalents, and platform availability matrix
|
||||
- Use `axiom-swiftui-animation-ref` for general SwiftUI animation (not symbol-specific)
|
||||
- Use `axiom-hig-ref` for broader icon design guidelines
|
||||
|
||||
## Example Prompts
|
||||
|
||||
#### 1. "My SF Symbol shows as a single flat color but I want it to have depth with multiple shades. How do I fix this?"
|
||||
> The skill covers rendering mode selection — Hierarchical for depth from a single color, Palette for explicit per-layer colors
|
||||
|
||||
#### 2. "I want my download button to animate when tapped, then show a spinning indicator while downloading, and animate to a checkmark when done."
|
||||
> The skill covers effect selection: Bounce for tap feedback, Breathe/Pulse for in-progress, Replace with content transition for completion
|
||||
|
||||
#### 3. "I'm trying to use the new Draw animations from SF Symbols 7 but the effect isn't playing."
|
||||
> The skill covers Draw On/Off implementation, playback modes, iOS 26 requirements, and common troubleshooting
|
||||
|
||||
#### 4. "How do I create a custom symbol that supports all rendering modes and the new Draw animation?"
|
||||
> The skill covers custom symbol authoring workflow, template layers, Draw annotation with guide points
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Rendering Mode Decision Tree
|
||||
|
||||
SF Symbols support 4 rendering modes. The right choice depends on your design intent.
|
||||
|
||||
### Quick Decision
|
||||
|
||||
```
|
||||
Need depth from ONE color? → Hierarchical
|
||||
Need specific colors per layer? → Palette
|
||||
Want Apple's curated colors? → Multicolor
|
||||
Just need a tinted icon? → Monochrome (default)
|
||||
```
|
||||
|
||||
### Monochrome
|
||||
|
||||
The default mode. Every layer renders in the same color (your `foregroundStyle`).
|
||||
|
||||
```swift
|
||||
Image(systemName: "cloud.rain.fill")
|
||||
.foregroundStyle(.blue)
|
||||
// All layers are blue
|
||||
```
|
||||
|
||||
**When to use**: Simple tinted icons, matching text color, toolbar items, tab bar items.
|
||||
|
||||
### Hierarchical
|
||||
|
||||
Renders layers at different opacities derived from a **single** color. Primary layers are fully opaque; secondary and tertiary layers get progressively more transparent. Creates depth without specifying multiple colors.
|
||||
|
||||
```swift
|
||||
Image(systemName: "cloud.rain.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundStyle(.blue)
|
||||
// Cloud is full blue, rain drops are lighter blue
|
||||
```
|
||||
|
||||
**When to use**: When you want visual depth but still want the icon to feel cohesive with a single hue. Most common choice for polished UI.
|
||||
|
||||
### Palette
|
||||
|
||||
Each layer gets an **explicit** color. Unlike Hierarchical, no automatic opacity derivation — you control each layer's color directly.
|
||||
|
||||
```swift
|
||||
Image(systemName: "cloud.rain.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(.blue, .cyan)
|
||||
// Cloud is blue, rain drops are cyan
|
||||
```
|
||||
|
||||
**When to use**: Branded icons, status indicators where specific colors carry meaning, designs requiring exact color control.
|
||||
|
||||
**Gotcha**: If you provide fewer colors than layers, extra layers reuse the last color. If the symbol has 3 layers and you provide 2 colors, the third layer uses the second color.
|
||||
|
||||
### Multicolor
|
||||
|
||||
Uses Apple's predefined color scheme for each symbol. Colors are fixed — you cannot customize them.
|
||||
|
||||
```swift
|
||||
Image(systemName: "cloud.rain.fill")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
// Cloud is white, rain drops are blue (Apple's design)
|
||||
```
|
||||
|
||||
**When to use**: Weather indicators, file type icons, or anywhere Apple's curated design intent matches your needs. Not all symbols support Multicolor — unsupported symbols fall back to Monochrome.
|
||||
|
||||
### Common Mistakes
|
||||
|
||||
| Mistake | Impact | Fix |
|
||||
|---------|--------|-----|
|
||||
| Using `.foregroundColor()` with Multicolor | Overrides Apple's colors | Remove foreground color modifier |
|
||||
| Setting Palette with only 1 color | Looks like Monochrome | Provide colors for each layer |
|
||||
| Assuming all symbols support Multicolor | Fallback to Monochrome | Check in SF Symbols app first |
|
||||
| Using Hierarchical when layers need distinct meanings | Colors don't carry semantic intent | Use Palette instead |
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Symbol Effects System
|
||||
|
||||
Symbol effects bring SF Symbols to life with motion. Every effect falls into one of four behavioral categories.
|
||||
|
||||
### Effect Categories
|
||||
|
||||
| Category | Trigger | Duration | Use Case |
|
||||
|----------|---------|----------|----------|
|
||||
| **Discrete** | Value change | One-shot | Tap feedback, event notification |
|
||||
| **Indefinite** | `isActive` bool | Continuous until stopped | Loading states, ongoing processes |
|
||||
| **Transition** | View insert/remove | One-shot | Appear/disappear with style |
|
||||
| **Content Transition** | Symbol swap | One-shot | Replacing one symbol with another |
|
||||
|
||||
### Which Effect for Which UX Purpose
|
||||
|
||||
```
|
||||
User tapped something → Bounce (discrete)
|
||||
Something changed, draw attention → Wiggle (discrete, iOS 18+)
|
||||
Ongoing process/loading → Pulse, Breathe, or Variable Color (indefinite)
|
||||
Rotation indicates progress → Rotate (indefinite, iOS 18+)
|
||||
Show/hide symbol → Appear/Disappear (transition)
|
||||
Swap between two symbols → Replace (content transition)
|
||||
Symbol enters with hand-drawn style → Draw On (iOS 26+)
|
||||
Symbol exits with hand-drawn style → Draw Off (iOS 26+)
|
||||
Progress indicator along path → Variable Draw (iOS 26+)
|
||||
Scale up/down for emphasis → Scale (indefinite)
|
||||
```
|
||||
|
||||
### Discrete Effects
|
||||
|
||||
Fire once when a value changes. The symbol performs the animation and returns to its resting state.
|
||||
|
||||
#### Bounce
|
||||
|
||||
The most common discrete effect. A brief, springy animation.
|
||||
|
||||
```swift
|
||||
@State private var downloadCount = 0
|
||||
|
||||
Image(systemName: "arrow.down.circle")
|
||||
.symbolEffect(.bounce, value: downloadCount)
|
||||
```
|
||||
|
||||
The animation triggers each time `downloadCount` changes.
|
||||
|
||||
**Directional options**: `.bounce.up`, `.bounce.down`
|
||||
|
||||
#### Wiggle (iOS 18+)
|
||||
|
||||
A horizontal shake that draws attention to the symbol.
|
||||
|
||||
```swift
|
||||
Image(systemName: "bell.fill")
|
||||
.symbolEffect(.wiggle, value: notificationCount)
|
||||
```
|
||||
|
||||
**Directional options**: `.wiggle.left`, `.wiggle.right`, `.wiggle.forward`, `.wiggle.backward`
|
||||
|
||||
`.forward` and `.backward` respect reading direction — use these for RTL support.
|
||||
|
||||
#### Rotate (as Discrete, iOS 18+)
|
||||
|
||||
A single rotation when triggered by value change.
|
||||
|
||||
```swift
|
||||
Image(systemName: "arrow.trianglehead.2.clockwise")
|
||||
.symbolEffect(.rotate, value: refreshCount)
|
||||
```
|
||||
|
||||
**Options**: `.rotate.clockwise`, `.rotate.counterClockwise`
|
||||
|
||||
**By Layer**: Some symbols rotate only specific layers (e.g., fan blades spin but the housing stays fixed). Use `.rotate.byLayer` to activate this.
|
||||
|
||||
### Indefinite Effects
|
||||
|
||||
Run continuously while `isActive` is `true`. Stop when `isActive` becomes `false`.
|
||||
|
||||
#### Pulse
|
||||
|
||||
A subtle opacity pulse. Good for "waiting" states.
|
||||
|
||||
```swift
|
||||
Image(systemName: "network")
|
||||
.symbolEffect(.pulse, isActive: isConnecting)
|
||||
```
|
||||
|
||||
#### Variable Color
|
||||
|
||||
Iterates through the symbol's layers, highlighting each in sequence. Creates a "filling up" or "cycling" look.
|
||||
|
||||
```swift
|
||||
Image(systemName: "wifi")
|
||||
.symbolEffect(.variableColor.iterative, isActive: isSearching)
|
||||
```
|
||||
|
||||
**Variants**:
|
||||
- `.variableColor.iterative` — highlights one layer at a time
|
||||
- `.variableColor.cumulative` — progressively fills layers
|
||||
- `.variableColor.reversing` — cycles back and forth
|
||||
- Combine: `.variableColor.iterative.reversing`
|
||||
|
||||
#### Scale
|
||||
|
||||
Scales the symbol up or down.
|
||||
|
||||
```swift
|
||||
Image(systemName: "mic.fill")
|
||||
.symbolEffect(.scale.up, isActive: isRecording)
|
||||
```
|
||||
|
||||
#### Breathe (iOS 18+)
|
||||
|
||||
A smooth, rhythmic scale animation — like the symbol is breathing.
|
||||
|
||||
```swift
|
||||
Image(systemName: "heart.fill")
|
||||
.symbolEffect(.breathe, isActive: isMonitoring)
|
||||
```
|
||||
|
||||
**Variants**: `.breathe.plain` (scale only), `.breathe.pulse` (scale + opacity)
|
||||
|
||||
#### Rotate (as Indefinite, iOS 18+)
|
||||
|
||||
Continuous rotation for processing indicators.
|
||||
|
||||
```swift
|
||||
Image(systemName: "gear")
|
||||
.symbolEffect(.rotate, isActive: isProcessing)
|
||||
```
|
||||
|
||||
### Effect Options
|
||||
|
||||
All effects accept `SymbolEffectOptions` via the `options` parameter.
|
||||
|
||||
```swift
|
||||
// Repeat 3 times
|
||||
.symbolEffect(.bounce, options: .repeat(3), value: count)
|
||||
|
||||
// Double speed
|
||||
.symbolEffect(.pulse, options: .speed(2.0), isActive: true)
|
||||
|
||||
// Repeat continuously
|
||||
.symbolEffect(.variableColor, options: .repeat(.continuous), isActive: true)
|
||||
|
||||
// Non-repeating (run once)
|
||||
.symbolEffect(.breathe, options: .nonRepeating, isActive: true)
|
||||
|
||||
// Combine options
|
||||
.symbolEffect(.bounce, options: .repeat(5).speed(1.5), value: count)
|
||||
```
|
||||
|
||||
### Transition Effects
|
||||
|
||||
Used when a symbol-based view appears or disappears from the view hierarchy.
|
||||
|
||||
```swift
|
||||
if showSymbol {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.transition(.symbolEffect(.appear))
|
||||
}
|
||||
```
|
||||
|
||||
**Available transitions**: `.appear`, `.disappear`
|
||||
|
||||
**Variants**: `.appear.up`, `.appear.down`, `.disappear.up`, `.disappear.down`
|
||||
|
||||
### Content Transitions
|
||||
|
||||
Used to animate from one symbol to another. Applied to the container, not the symbol.
|
||||
|
||||
```swift
|
||||
@State private var isFavorite = false
|
||||
|
||||
Button {
|
||||
isFavorite.toggle()
|
||||
} label: {
|
||||
Image(systemName: isFavorite ? "star.fill" : "star")
|
||||
.contentTransition(.symbolEffect(.replace))
|
||||
}
|
||||
```
|
||||
|
||||
**Replace variants**:
|
||||
- `.replace.downUp` — old symbol moves down, new moves up
|
||||
- `.replace.upUp` — both move up
|
||||
- `.replace.offUp` — old fades off, new moves up
|
||||
|
||||
#### Magic Replace
|
||||
|
||||
When two symbols share a common structure (like `star` and `star.fill`, or `pause.fill` and `play.fill`), Replace automatically performs a **Magic Replace** — morphing shared elements while transitioning differing parts. Magic Replace is the default behavior for `.replace` in iOS 18+. For explicit control:
|
||||
|
||||
```swift
|
||||
// Explicit Magic Replace with fallback
|
||||
.contentTransition(.symbolEffect(.replace.magic(fallback: .replace.downUp)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 3: SF Symbols 7 — Draw Animations (iOS 26+)
|
||||
|
||||
Draw animations simulate the natural flow of drawing a symbol with a pen. This is the signature new feature in SF Symbols 7.
|
||||
|
||||
### Draw On and Draw Off
|
||||
|
||||
**Draw On** animates a symbol appearing by "drawing" it stroke by stroke.
|
||||
**Draw Off** animates a symbol disappearing by "erasing" it.
|
||||
|
||||
```swift
|
||||
// Draw On — symbol draws in when isComplete becomes true
|
||||
Image(systemName: "checkmark.circle")
|
||||
.symbolEffect(.drawOn, isActive: isComplete)
|
||||
|
||||
// Draw Off — symbol draws out when isHidden becomes true
|
||||
Image(systemName: "star.fill")
|
||||
.symbolEffect(.drawOff, isActive: isHidden)
|
||||
```
|
||||
|
||||
### Playback Modes
|
||||
|
||||
Control how multi-layer symbols animate their draw:
|
||||
|
||||
```swift
|
||||
// By Layer (default) — staggered timing, layers overlap
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.symbolEffect(.drawOn.byLayer, isActive: showIcon)
|
||||
|
||||
// Whole Symbol — all layers draw simultaneously
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.symbolEffect(.drawOn.wholeSymbol, isActive: showIcon)
|
||||
|
||||
// Individually — sequential, each layer completes before next starts
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.symbolEffect(.drawOn.individually, isActive: showIcon)
|
||||
```
|
||||
|
||||
**When to use each mode**:
|
||||
- **By Layer** (default): Most natural feel, good for most symbols
|
||||
- **Whole Symbol**: When the symbol should appear as one unit, not in parts
|
||||
- **Individually**: When you want to emphasize each layer separately (storytelling, onboarding)
|
||||
|
||||
### Draw Off Direction
|
||||
|
||||
Draw Off supports controlling whether the animation plays forward or in reverse:
|
||||
|
||||
```swift
|
||||
// Forward (default) — follows the draw path
|
||||
.symbolEffect(.drawOff.nonReversed, isActive: isHidden)
|
||||
|
||||
// Reversed — erases in reverse order of how it was drawn
|
||||
.symbolEffect(.drawOff.reversed, isActive: isErasing)
|
||||
```
|
||||
|
||||
### Variable Draw
|
||||
|
||||
Variable Draw uses `SymbolVariableValueMode.draw` to partially draw a symbol's stroke path based on a 0.0 to 1.0 value — perfect for progress indicators.
|
||||
|
||||
```swift
|
||||
Image(systemName: "thermometer.high", variableValue: temperature)
|
||||
.symbolVariableValueMode(.draw) // iOS 26+
|
||||
```
|
||||
|
||||
Compare with traditional Variable Color (which sets opacity per layer):
|
||||
|
||||
```swift
|
||||
Image(systemName: "wifi", variableValue: signalStrength)
|
||||
.symbolVariableValueMode(.color) // iOS 17+ (default behavior)
|
||||
```
|
||||
|
||||
**Constraint**: A symbol can support both Variable Color and Variable Draw, but only one mode can be active at render time. Setting an unsupported mode has no visible effect.
|
||||
|
||||
### Gradient Rendering
|
||||
|
||||
SF Symbols 7 introduces `SymbolColorRenderingMode` for gradient fills generated from a single source color.
|
||||
|
||||
```swift
|
||||
Image(systemName: "star.fill")
|
||||
.symbolColorRenderingMode(.gradient) // iOS 26+
|
||||
.foregroundStyle(.red)
|
||||
```
|
||||
|
||||
| Mode | Description |
|
||||
|------|-------------|
|
||||
| `.flat` | Solid color fill (default) |
|
||||
| `.gradient` | Axial gradient from source color |
|
||||
|
||||
Gradients work with all rendering modes and are most effective at larger sizes.
|
||||
|
||||
### Magic Replace with Draw
|
||||
|
||||
When using `.contentTransition(.symbolEffect(.replace))` between certain symbol pairs, the system now combines Draw Off on the outgoing symbol with Draw On for the incoming symbol. The enclosure (if shared, like a circle outline) is preserved while inner elements transition with draw animations.
|
||||
|
||||
```swift
|
||||
// Automatic Draw-enhanced Magic Replace
|
||||
Image(systemName: isComplete ? "checkmark.circle.fill" : "circle")
|
||||
.contentTransition(.symbolEffect(.replace))
|
||||
```
|
||||
|
||||
### Custom Symbol Draw Annotation
|
||||
|
||||
To enable Draw animations on custom symbols, annotate paths in the SF Symbols app:
|
||||
|
||||
1. **Open** your custom symbol in SF Symbols 7
|
||||
2. **Select** a path layer
|
||||
3. **Add guide points** to define draw direction:
|
||||
- **Start point** (open circle): Where drawing begins
|
||||
- **End point** (closed circle): Where drawing ends
|
||||
- **Corner point** (diamond): Sharp direction changes
|
||||
- **Bidirectional point**: Enables center-outward drawing
|
||||
- **Attachment point**: Connects non-drawing decorative elements
|
||||
4. **Minimum**: Two guide points per path (start and end)
|
||||
5. **Test** using the Preview panel in SF Symbols app
|
||||
|
||||
**Option-drag** guide points for precise placement. Use context menus to configure direction and end caps.
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Anti-Patterns
|
||||
|
||||
### Wrong Rendering Mode
|
||||
|
||||
| Pattern | Problem | Fix |
|
||||
|---------|---------|-----|
|
||||
| Palette with 1 color | Equivalent to Monochrome, wasted API call | Use Monochrome or provide multiple colors |
|
||||
| Multicolor for branded icons | Can't customize Apple's fixed colors | Use Palette with brand colors |
|
||||
| Hardcoded `.foregroundColor(.blue)` | Ignores Dark Mode, Dynamic Type, accessibility | Use `.foregroundStyle()` with semantic colors |
|
||||
| Hierarchical for status indicators | Layers don't carry distinct meaning | Use Palette with semantic colors |
|
||||
|
||||
### Wrong Effect Choice
|
||||
|
||||
| Pattern | Problem | Fix |
|
||||
|---------|---------|-----|
|
||||
| Bounce for loading state | One-shot, doesn't convey "ongoing" | Use Pulse, Breathe, or Variable Color |
|
||||
| Pulse for tap feedback | Too subtle for confirming action | Use Bounce |
|
||||
| Continuous Rotate for non-mechanical symbols | Looks unnatural for organic shapes | Use Breathe for organic symbols |
|
||||
| Draw On for transient state changes | Too dramatic for frequent toggles | Use Replace or Scale |
|
||||
|
||||
### Missing iOS Version Checks
|
||||
|
||||
```swift
|
||||
// ❌ Crashes on iOS 17
|
||||
Image(systemName: "bell")
|
||||
.symbolEffect(.wiggle, value: count) // Wiggle requires iOS 18+
|
||||
|
||||
// ✅ Safe version check
|
||||
Image(systemName: "bell")
|
||||
.modifier(BellEffectModifier(count: count))
|
||||
|
||||
struct BellEffectModifier: ViewModifier {
|
||||
let count: Int
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 18, *) {
|
||||
content.symbolEffect(.wiggle, value: count)
|
||||
} else {
|
||||
content.symbolEffect(.bounce, value: count)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Ignoring Reduce Motion
|
||||
|
||||
Symbol effects **automatically** respect the Reduce Motion accessibility setting — most effects are suppressed or simplified. However, if you're using effects to convey essential information (not just decoration), provide an alternative:
|
||||
|
||||
```swift
|
||||
// Variable Color conveys WiFi strength — provide text fallback
|
||||
Image(systemName: "wifi")
|
||||
.symbolEffect(.variableColor, isActive: isSearching)
|
||||
.accessibilityLabel("Searching for WiFi networks")
|
||||
```
|
||||
|
||||
**Do not** disable Reduce Motion or try to force-play effects. The system handles this correctly.
|
||||
|
||||
### Missing Accessibility Labels
|
||||
|
||||
```swift
|
||||
// ❌ VoiceOver says "star.fill"
|
||||
Image(systemName: "star.fill")
|
||||
|
||||
// ✅ VoiceOver says "Favorite"
|
||||
Image(systemName: "star.fill")
|
||||
.accessibilityLabel("Favorite")
|
||||
```
|
||||
|
||||
When using `.contentTransition(.symbolEffect(.replace))` to swap symbols, update the accessibility label to match the current state:
|
||||
|
||||
```swift
|
||||
Image(systemName: isFavorite ? "star.fill" : "star")
|
||||
.contentTransition(.symbolEffect(.replace))
|
||||
.accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 5: Troubleshooting
|
||||
|
||||
### Effect Not Playing
|
||||
|
||||
**Symptom**: `.symbolEffect()` modifier applied but no animation visible.
|
||||
|
||||
1. **Check iOS version** — Bounce/Pulse/Scale require iOS 17+, Wiggle/Rotate/Breathe require iOS 18+, Draw requires iOS 26+
|
||||
2. **Check Reduce Motion** — Settings > Accessibility > Motion > Reduce Motion. If on, most effects are suppressed
|
||||
3. **Check trigger type** — Discrete effects need `value:` that changes. Indefinite effects need `isActive: true`. Transition effects need the view to actually enter/leave the hierarchy
|
||||
4. **Check symbol compatibility** — Not all symbols support all effects. Open the SF Symbols app, select the symbol, and check the Animation inspector
|
||||
5. **Check for conflicting effects** — Multiple `.symbolEffect()` modifiers on the same view can conflict. Use a single effect or combine with options
|
||||
|
||||
### Wrong Colors in Rendering Mode
|
||||
|
||||
**Symptom**: Symbol colors don't match expected appearance.
|
||||
|
||||
1. **Check rendering mode** — If you set `.foregroundStyle` but see only one color, you may need `.symbolRenderingMode(.palette)` or `.hierarchical`
|
||||
2. **Check `.tint` vs `.foregroundStyle`** — In UIKit, `tintColor` affects Monochrome and Hierarchical. For Palette, use `UIImage.SymbolConfiguration(paletteColors:)`
|
||||
3. **Check Multicolor support** — Not all symbols have Multicolor variants. Unsupported symbols fall back to Monochrome
|
||||
4. **Check environment** — `.foregroundStyle` from a parent view may override your rendering mode. Apply `.symbolRenderingMode()` directly on the Image
|
||||
|
||||
### Custom Symbol Weight Mismatch
|
||||
|
||||
**Symptom**: Custom symbol looks too thin or too thick next to text or other symbols.
|
||||
|
||||
1. **Check template weight** — Custom symbols need weight variants matching the 9 SF Pro weights. Export from SF Symbols app handles this
|
||||
2. **Check `.font()` alignment** — The symbol's weight follows the applied font weight. If using `.font(.title)`, ensure your custom symbol has appropriate weight variants
|
||||
3. **Check scale** — `.imageScale(.small/.medium/.large)` affects overall size. Use `.font()` for weight matching
|
||||
|
||||
### Draw Animation Not Working on Custom Symbol
|
||||
|
||||
**Symptom**: `.symbolEffect(.drawOn)` applied to custom symbol but no draw animation occurs.
|
||||
|
||||
1. **Check guide points** — Custom symbols need Draw annotation with at least 2 guide points per path (start + end)
|
||||
2. **Check SF Symbols app version** — Draw annotation requires SF Symbols 7+
|
||||
3. **Check path structure** — Guide points must be placed on stroked paths, not fills. Convert fills to strokes where draw animation is desired
|
||||
4. **Check layer structure** — Each annotatable layer needs its own guide points
|
||||
|
||||
---
|
||||
|
||||
## Part 6: Pressure Scenarios
|
||||
|
||||
### Scenario 1: "Just use a static image, symbols are overkill"
|
||||
|
||||
**Setup**: Designer provides PNG icons. Developer considers using them instead of SF Symbols.
|
||||
|
||||
**Why this matters**: Static PNGs don't adapt to Dynamic Type, Bold Text, Dark Mode, or accessibility settings. They also don't support symbol effects.
|
||||
|
||||
**Professional response**: "SF Symbols scale with text, support 9 weights, adapt to Dark Mode and Bold Text automatically, and enable animations without custom code. A PNG requires @1x/@2x/@3x variants, manual Dark Mode handling, manual Dynamic Type scaling, and custom animation code. The 10 minutes to find the right SF Symbol saves hours of asset management."
|
||||
|
||||
**Time cost of skipping**: 2-4 hours managing assets + ongoing maintenance vs 10 minutes finding the right symbol.
|
||||
|
||||
### Scenario 2: "We'll add animations later"
|
||||
|
||||
**Setup**: Sprint deadline. PM says animations are polish and can wait.
|
||||
|
||||
**Why this matters**: Retrofitting symbol effects requires restructuring state management. Effects triggered by `value:` changes need the right state architecture from the start.
|
||||
|
||||
**Professional response**: "Adding `.symbolEffect(.bounce, value: count)` takes one line. Retrofitting the state to support it later takes a refactor. Let me add the effect now — it's literally one modifier."
|
||||
|
||||
### Scenario 3: "Draw animations look janky on our custom symbols"
|
||||
|
||||
**Setup**: Custom symbols have Draw animations that look wrong — paths draw in unexpected order or direction.
|
||||
|
||||
**Why this matters**: Draw annotation requires intentional guide point placement. Without it, the system guesses and often gets it wrong.
|
||||
|
||||
**Fix**: Open custom symbols in SF Symbols 7 app, add guide points explicitly to each path defining start/end/direction. Test each weight variant. See Custom Symbol Draw Annotation section above.
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
**WWDC**: 2023-10257, 2023-10258, 2024-10188, 2025-337
|
||||
|
||||
**Docs**: /symbols, /symbols/symboleffect, /symbols/symbolrenderingmode, /swiftui/image/symboleffect(_:options:value:), /swiftui/image/symbolrenderingmode(_:)
|
||||
|
||||
**Skills**: axiom-sf-symbols-ref, axiom-hig-ref, axiom-swiftui-animation-ref
|
||||
|
||||
---
|
||||
|
||||
**Last Updated** Based on WWDC 2023/10257-10258, WWDC 2024/10188, WWDC 2025/337
|
||||
**Version** iOS 17+ (effects), iOS 18+ (Wiggle/Rotate/Breathe), iOS 26+ (Draw On/Off, Variable Draw, Gradients)
|
||||
3
.claude/skills/axiom-sf-symbols/agents/openai.yaml
Normal file
3
.claude/skills/axiom-sf-symbols/agents/openai.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
interface:
|
||||
display_name: "SF Symbols"
|
||||
short_description: "Implementing SF Symbols rendering modes, symbol effects, animations, custom symbols, or troubleshooting symbol appear..."
|
||||
Reference in New Issue
Block a user