axiom-sf-symbols by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-sf-symbols在以下情况时使用:
axiom-sf-symbols-ref 获取包含所有修饰符、UIKit 等效项和平台可用性矩阵的完整 API 参考axiom-swiftui-animation-ref 获取通用的 SwiftUI 动画(非符号专用)参考axiom-hig-ref 获取更广泛的图标设计指南此技能涵盖渲染模式选择——使用层次模式可从单一颜色获得深度感,使用调色板模式可为每层指定明确的颜色
此技能涵盖效果选择:弹跳用于点击反馈,呼吸/脉动用于进行中状态,替换内容过渡用于完成状态
此技能涵盖绘制开/关的实现、播放模式、iOS 26 要求以及常见问题排查
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
此技能涵盖自定义符号创作工作流、模板图层、使用引导点进行绘制注释
SF Symbols 支持 4 种渲染模式。正确的选择取决于你的设计意图。
需要从一种颜色获得深度感? → 层次模式
需要为每层指定特定颜色? → 调色板模式
想要 Apple 精心设计的颜色? → 多色模式
只需要一个着色的图标? → 单色模式(默认)
默认模式。每一层都以相同的颜色(你的 foregroundStyle)渲染。
Image(systemName: "cloud.rain.fill")
.foregroundStyle(.blue)
// 所有图层都是蓝色
何时使用:简单的着色图标、匹配文本颜色、工具栏项目、标签栏项目。
使用从单一颜色派生的不同不透明度渲染图层。主要图层完全不透明;次要和三级图层逐渐变得更加透明。无需指定多种颜色即可创建深度感。
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
// 云朵是完全蓝色,雨滴是较浅的蓝色
何时使用:当你想要视觉深度,但仍希望图标感觉与单一色调协调一致时。这是打造精致用户界面的最常见选择。
每层获得一个明确的颜色。与层次模式不同,没有自动的不透明度派生——你直接控制每层的颜色。
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.blue, .cyan)
// 云朵是蓝色,雨滴是青色
何时使用:品牌图标、特定颜色具有含义的状态指示器、需要精确颜色控制的设计。
注意:如果你提供的颜色数量少于图层数量,额外的图层会重复使用最后一个颜色。如果符号有 3 个图层而你只提供了 2 种颜色,则第三层使用第二种颜色。
使用 Apple 为每个符号预定义的颜色方案。颜色是固定的——你无法自定义它们。
Image(systemName: "cloud.rain.fill")
.symbolRenderingMode(.multicolor)
// 云朵是白色,雨滴是蓝色(Apple 的设计)
何时使用:天气指示器、文件类型图标,或任何 Apple 精心设计的设计意图符合你需求的地方。并非所有符号都支持多色模式——不支持的符号会回退到单色模式。
| 错误 | 影响 | 修复方法 |
|---|---|---|
对多色模式使用 .foregroundColor() | 覆盖了 Apple 的颜色 | 移除前景色修饰符 |
| 调色板模式只设置一种颜色 | 看起来像单色模式 | 为每个图层提供颜色 |
| 假设所有符号都支持多色模式 | 回退到单色模式 | 先在 SF Symbols 应用中检查 |
| 当图层需要不同含义时使用层次模式 | 颜色不传达语义意图 | 改用调色板模式 |
符号效果通过运动为 SF Symbols 注入活力。每种效果都属于四种行为类别之一。
| 类别 | 触发条件 | 持续时间 | 使用场景 |
|---|---|---|---|
| 离散效果 | 值变化 | 单次触发 | 点击反馈、事件通知 |
| 无限效果 | isActive 布尔值 | 持续直到停止 | 加载状态、进行中的过程 |
| 过渡效果 | 视图插入/移除 | 单次触发 | 以特定风格出现/消失 |
| 内容过渡效果 | 符号交换 | 单次触发 | 将一个符号替换为另一个 |
用户点击了某物 → 弹跳(离散效果)
某物发生了变化,需要引起注意 → 摆动(离散效果,iOS 18+)
进行中的过程/加载 → 脉动、呼吸或可变颜色(无限效果)
旋转表示进度 → 旋转(无限效果,iOS 18+)
显示/隐藏符号 → 出现/消失(过渡效果)
在两个符号之间切换 → 替换(内容过渡效果)
符号以手绘风格进入 → 绘制开(iOS 26+)
符号以手绘风格退出 → 绘制关(iOS 26+)
沿路径的进度指示器 → 可变绘制(iOS 26+)
放大/缩小以强调 → 缩放(无限效果)
当值发生变化时触发一次。符号执行动画并返回到其静止状态。
最常见的离散效果。一个短暂、有弹性的动画。
@State private var downloadCount = 0
Image(systemName: "arrow.down.circle")
.symbolEffect(.bounce, value: downloadCount)
每次 downloadCount 变化时触发动画。
方向选项:.bounce.up、.bounce.down
一个水平摇动,用于吸引对符号的注意。
Image(systemName: "bell.fill")
.symbolEffect(.wiggle, value: notificationCount)
方向选项:.wiggle.left、.wiggle.right、.wiggle.forward、.wiggle.backward
.forward 和 .backward 会遵循阅读方向——使用这些选项以支持从右到左的语言。
当由值变化触发时,执行单次旋转。
Image(systemName: "arrow.trianglehead.2.clockwise")
.symbolEffect(.rotate, value: refreshCount)
选项:.rotate.clockwise、.rotate.counterClockwise
按图层:某些符号仅旋转特定图层(例如,风扇叶片旋转但外壳保持固定)。使用 .rotate.byLayer 来激活此功能。
当 isActive 为 true 时持续运行。当 isActive 变为 false 时停止。
一种微妙的不透明度脉动。适用于"等待"状态。
Image(systemName: "network")
.symbolEffect(.pulse, isActive: isConnecting)
遍历符号的图层,依次高亮显示每个图层。创建一种"填充"或"循环"的外观。
Image(systemName: "wifi")
.symbolEffect(.variableColor.iterative, isActive: isSearching)
变体:
.variableColor.iterative — 一次高亮一个图层.variableColor.cumulative — 逐步填充图层.variableColor.reversing — 来回循环.variableColor.iterative.reversing放大或缩小符号。
Image(systemName: "mic.fill")
.symbolEffect(.scale.up, isActive: isRecording)
一种平滑、有节奏的缩放动画——就像符号在呼吸一样。
Image(systemName: "heart.fill")
.symbolEffect(.breathe, isActive: isMonitoring)
变体:.breathe.plain(仅缩放)、.breathe.pulse(缩放 + 不透明度)
用于处理指示器的连续旋转。
Image(systemName: "gear")
.symbolEffect(.rotate, isActive: isProcessing)
所有效果都通过 options 参数接受 SymbolEffectOptions。
// 重复 3 次
.symbolEffect(.bounce, options: .repeat(3), value: count)
// 双倍速度
.symbolEffect(.pulse, options: .speed(2.0), isActive: true)
// 连续重复
.symbolEffect(.variableColor, options: .repeat(.continuous), isActive: true)
// 非重复(运行一次)
.symbolEffect(.breathe, options: .nonRepeating, isActive: true)
// 组合选项
.symbolEffect(.bounce, options: .repeat(5).speed(1.5), value: count)
当基于符号的视图在视图层次结构中出现或消失时使用。
if showSymbol {
Image(systemName: "checkmark.circle.fill")
.transition(.symbolEffect(.appear))
}
可用的过渡效果:.appear、.disappear
变体:.appear.up、.appear.down、.disappear.up、.disappear.down
用于从一个符号动画过渡到另一个符号。应用于容器,而不是符号本身。
@State private var isFavorite = false
Button {
isFavorite.toggle()
} label: {
Image(systemName: isFavorite ? "star.fill" : "star")
.contentTransition(.symbolEffect(.replace))
}
替换变体:
.replace.downUp — 旧符号向下移动,新符号向上移动.replace.upUp — 两者都向上移动.replace.offUp — 旧符号淡出,新符号向上移动当两个符号共享一个共同结构时(如 star 和 star.fill,或 pause.fill 和 play.fill),替换效果会自动执行神奇替换——变形共享元素,同时过渡不同的部分。神奇替换是 iOS 18+ 中 .replace 的默认行为。要进行显式控制:
// 带有回退的显式神奇替换
.contentTransition(.symbolEffect(.replace.magic(fallback: .replace.downUp)))
绘制动画模拟用笔绘制符号的自然流程。这是 SF Symbols 7 的标志性新功能。
绘制开通过逐笔划"绘制"来动画显示符号的出现。绘制关通过"擦除"来动画显示符号的消失。
// 绘制开 — 当 isComplete 变为 true 时,符号绘制进入
Image(systemName: "checkmark.circle")
.symbolEffect(.drawOn, isActive: isComplete)
// 绘制关 — 当 isHidden 变为 true 时,符号绘制退出
Image(systemName: "star.fill")
.symbolEffect(.drawOff, isActive: isHidden)
控制多层符号如何动画化其绘制过程:
// 按图层(默认)— 错开的时间,图层重叠
Image(systemName: "square.and.arrow.up")
.symbolEffect(.drawOn.byLayer, isActive: showIcon)
// 整体符号 — 所有图层同时绘制
Image(systemName: "square.and.arrow.up")
.symbolEffect(.drawOn.wholeSymbol, isActive: showIcon)
// 单独 — 顺序进行,每个图层完成后再开始下一个
Image(systemName: "square.and.arrow.up")
.symbolEffect(.drawOn.individually, isActive: showIcon)
何时使用每种模式:
绘制关支持控制动画是正向播放还是反向播放:
// 正向(默认)— 遵循绘制路径
.symbolEffect(.drawOff.nonReversed, isActive: isHidden)
// 反向 — 按照绘制顺序的相反顺序擦除
.symbolEffect(.drawOff.reversed, isActive: isErasing)
可变绘制使用 SymbolVariableValueMode.draw 根据 0.0 到 1.0 的值部分绘制符号的笔划路径——非常适合进度指示器。
Image(systemName: "thermometer.high", variableValue: temperature)
.symbolVariableValueMode(.draw) // iOS 26+
与传统的可变颜色(为每层设置不透明度)进行比较:
Image(systemName: "wifi", variableValue: signalStrength)
.symbolVariableValueMode(.color) // iOS 17+(默认行为)
限制:一个符号可以同时支持可变颜色和可变绘制,但在渲染时只能激活一种模式。设置不支持的模式没有可见效果。
SF Symbols 7 引入了 SymbolColorRenderingMode,用于从单一源颜色生成渐变填充。
Image(systemName: "star.fill")
.symbolColorRenderingMode(.gradient) // iOS 26+
.foregroundStyle(.red)
| 模式 | 描述 |
|---|---|
.flat | 纯色填充(默认) |
.gradient | 从源颜色生成的轴向渐变 |
渐变适用于所有渲染模式,并且在较大尺寸下效果最佳。
当在特定符号对之间使用 .contentTransition(.symbolEffect(.replace)) 时,系统现在会将退出符号的绘制关与进入符号的绘制开结合起来。外壳(如果共享,如圆形轮廓)会被保留,而内部元素则通过绘制动画进行过渡。
// 自动增强的绘制神奇替换
Image(systemName: isComplete ? "checkmark.circle.fill" : "circle")
.contentTransition(.symbolEffect(.replace))
要在自定义符号上启用绘制动画,请在 SF Symbols 应用中注释路径:
按住 Option 键拖动引导点以进行精确放置。使用上下文菜单配置方向和端点样式。
| 模式 | 问题 | 修复方法 |
|---|---|---|
| 调色板模式只使用一种颜色 | 等同于单色模式,浪费了 API 调用 | 使用单色模式或提供多种颜色 |
| 品牌图标使用多色模式 | 无法自定义 Apple 的固定颜色 | 使用带有品牌颜色的调色板模式 |
硬编码 .foregroundColor(.blue) | 忽略深色模式、动态类型、无障碍访问 | 使用带有语义颜色的 .foregroundStyle() |
| 状态指示器使用层次模式 | 图层不传达不同的含义 | 使用带有语义颜色的调色板模式 |
| 模式 | 问题 | 修复方法 |
|---|---|---|
| 加载状态使用弹跳效果 | 单次触发,无法传达"进行中" | 使用脉动、呼吸或可变颜色 |
| 点击反馈使用脉动效果 | 对于确认操作来说过于微妙 | 使用弹跳效果 |
| 非机械符号使用连续旋转 | 对于有机形状看起来不自然 | 有机符号使用呼吸效果 |
| 短暂状态变化使用绘制开效果 | 对于频繁切换来说过于夸张 | 使用替换或缩放效果 |
// ❌ 在 iOS 17 上崩溃
Image(systemName: "bell")
.symbolEffect(.wiggle, value: count) // 摆动效果需要 iOS 18+
// ✅ 安全版本检查
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)
}
}
}
符号效果自动遵循减少动态效果的无障碍访问设置——大多数效果会被抑制或简化。然而,如果你使用效果来传达重要信息(而不仅仅是装饰),请提供替代方案:
// 可变颜色传达 WiFi 强度 — 提供文本回退
Image(systemName: "wifi")
.symbolEffect(.variableColor, isActive: isSearching)
.accessibilityLabel("正在搜索 WiFi 网络")
不要禁用减少动态效果或尝试强制播放效果。系统会正确处理这一点。
// ❌ VoiceOver 会说"star.fill"
Image(systemName: "star.fill")
// ✅ VoiceOver 会说"收藏"
Image(systemName: "star.fill")
.accessibilityLabel("收藏")
当使用 .contentTransition(.symbolEffect(.replace)) 来交换符号时,更新无障碍访问标签以匹配当前状态:
Image(systemName: isFavorite ? "star.fill" : "star")
.contentTransition(.symbolEffect(.replace))
.accessibilityLabel(isFavorite ? "从收藏中移除" : "添加到收藏")
症状:应用了 .symbolEffect() 修饰符,但看不到动画。
value:。无限效果需要 isActive: true。过渡效果需要视图实际进入/离开视图层次结构.symbolEffect() 修饰符可能会冲突。使用单一效果或与选项组合使用症状:符号颜色与预期外观不匹配。
.foregroundStyle 但只看到一种颜色,你可能需要 .symbolRenderingMode(.palette) 或 .hierarchical.tint 与 .foregroundStyle — 在 UIKit 中,tintColor 影响单色和层次模式。对于调色板模式,使用 UIImage.SymbolConfiguration(paletteColors:).foregroundStyle 可能会覆盖你的渲染模式。直接在 Image 上应用 .symbolRenderingMode()症状:自定义符号在文本或其他符号旁边看起来太细或太粗。
.font() 对齐 — 符号的字重遵循应用的字体字重。如果使用 .font(.title),请确保你的自定义符号具有适当的字重变体.imageScale(.small/.medium/.large) 影响整体大小。使用 .font() 进行字重匹配症状:对自定义符号应用了 .symbolEffect(.drawOn),但没有绘制动画发生。
场景:设计师提供了 PNG 图标。开发者考虑使用它们而不是 SF Symbols。
为何重要:静态 PNG 无法适应动态类型、粗体文本、深色模式或无障碍访问设置。它们也不支持符号效果。
专业回应:"SF Symbols 会随文本缩放,支持 9 种字重,自动适应深色模式和粗体文本,并且无需自定义代码即可启用动画。PNG 需要 @1x/@2x/@3x 变体、手动处理深色模式、手动处理动态类型缩放以及自定义动画代码。花 10 分钟找到合适的 SF Symbol 可以节省数小时的资源管理时间。"
跳过的成本:2-4 小时管理资源 + 持续维护 vs 10 分钟找到合适的符号。
场景:冲刺截止日期临近。产品经理说动画是润色,可以等待。
为何重要:后期添加符号效果需要重构状态管理。由 value: 变化触发的效果需要从一开始就具备正确的状态架构。
专业回应:"添加 .symbolEffect(.bounce, value: count) 只需要一行代码。后期为了支持它而重构状态需要一次重构。让我现在就添加这个效果——它真的只是一个修饰符。"
场景:自定义符号的绘制动画看起来不对——路径以意外的顺序或方向绘制。
为何重要:绘制注释需要有意识地放置引导点。没有它,系统会猜测,并且经常出错。
修复方法:在 SF Symbols 7 应用中打开自定义符号,为每个路径显式添加引导点以定义起点/终点/方向。测试每个字重变体。参见上面的自定义符号绘制注释部分。
WWDC:2023-10257, 2023-10258, 2024-10188, 2025-337
文档:/symbols, /symbols/symboleffect, /symbols/symbolrenderingmode, /swiftui/image/symboleffect(:options:value:), /swiftui/image/symbolrenderingmode( :)
技能:axiom-sf-symbols-ref, axiom-hig-ref, axiom-swiftui-animation-ref
最后更新 基于 WWDC 2023/10257-10258, WWDC 2024/10188, WWDC 2025/337 版本 iOS 17+(效果), iOS 18+(摆动/旋转/呼吸), iOS 26+(绘制开/关、可变绘制、渐变)
每周安装次数
72
代码仓库
GitHub 星标数
610
首次出现
2026年2月7日
安全审计
安装于
opencode67
gemini-cli64
codex64
kimi-cli61
github-copilot61
amp60
Use when:
axiom-sf-symbols-ref for complete API reference with all modifiers, UIKit equivalents, and platform availability matrixaxiom-swiftui-animation-ref for general SwiftUI animation (not symbol-specific)axiom-hig-ref for broader icon design guidelinesThe skill covers rendering mode selection — Hierarchical for depth from a single color, Palette for explicit per-layer colors
The skill covers effect selection: Bounce for tap feedback, Breathe/Pulse for in-progress, Replace with content transition for completion
The skill covers Draw On/Off implementation, playback modes, iOS 26 requirements, and common troubleshooting
The skill covers custom symbol authoring workflow, template layers, Draw annotation with guide points
SF Symbols support 4 rendering modes. The right choice depends on your design intent.
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)
The default mode. Every layer renders in the same color (your foregroundStyle).
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.
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.
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.
Each layer gets an explicit color. Unlike Hierarchical, no automatic opacity derivation — you control each layer's color directly.
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.
Uses Apple's predefined color scheme for each symbol. Colors are fixed — you cannot customize them.
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.
| 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 |
Symbol effects bring SF Symbols to life with motion. Every effect falls into one of four behavioral 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 |
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)
Fire once when a value changes. The symbol performs the animation and returns to its resting state.
The most common discrete effect. A brief, springy animation.
@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
A horizontal shake that draws attention to the symbol.
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.
A single rotation when triggered by value change.
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.
Run continuously while isActive is true. Stop when isActive becomes false.
A subtle opacity pulse. Good for "waiting" states.
Image(systemName: "network")
.symbolEffect(.pulse, isActive: isConnecting)
Iterates through the symbol's layers, highlighting each in sequence. Creates a "filling up" or "cycling" look.
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.variableColor.iterative.reversingScales the symbol up or down.
Image(systemName: "mic.fill")
.symbolEffect(.scale.up, isActive: isRecording)
A smooth, rhythmic scale animation — like the symbol is breathing.
Image(systemName: "heart.fill")
.symbolEffect(.breathe, isActive: isMonitoring)
Variants : .breathe.plain (scale only), .breathe.pulse (scale + opacity)
Continuous rotation for processing indicators.
Image(systemName: "gear")
.symbolEffect(.rotate, isActive: isProcessing)
All effects accept SymbolEffectOptions via the options parameter.
// 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)
Used when a symbol-based view appears or disappears from the view hierarchy.
if showSymbol {
Image(systemName: "checkmark.circle.fill")
.transition(.symbolEffect(.appear))
}
Available transitions : .appear, .disappear
Variants : .appear.up, .appear.down, .disappear.up, .disappear.down
Used to animate from one symbol to another. Applied to the container, not the symbol.
@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 upWhen 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:
// Explicit Magic Replace with fallback
.contentTransition(.symbolEffect(.replace.magic(fallback: .replace.downUp)))
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 animates a symbol appearing by "drawing" it stroke by stroke. Draw Off animates a symbol disappearing by "erasing" it.
// 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)
Control how multi-layer symbols animate their draw:
// 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 :
Draw Off supports controlling whether the animation plays forward or in reverse:
// 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 uses SymbolVariableValueMode.draw to partially draw a symbol's stroke path based on a 0.0 to 1.0 value — perfect for progress indicators.
Image(systemName: "thermometer.high", variableValue: temperature)
.symbolVariableValueMode(.draw) // iOS 26+
Compare with traditional Variable Color (which sets opacity per layer):
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.
SF Symbols 7 introduces SymbolColorRenderingMode for gradient fills generated from a single source color.
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.
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.
// Automatic Draw-enhanced Magic Replace
Image(systemName: isComplete ? "checkmark.circle.fill" : "circle")
.contentTransition(.symbolEffect(.replace))
To enable Draw animations on custom symbols, annotate paths in the SF Symbols app:
Option-drag guide points for precise placement. Use context menus to configure direction and end caps.
| 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 |
| 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 |
// ❌ 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)
}
}
}
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:
// 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.
// ❌ 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:
Image(systemName: isFavorite ? "star.fill" : "star")
.contentTransition(.symbolEffect(.replace))
.accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
Symptom : .symbolEffect() modifier applied but no animation visible.
value: that changes. Indefinite effects need isActive: true. Transition effects need the view to actually enter/leave the hierarchy.symbolEffect() modifiers on the same view can conflict. Use a single effect or combine with optionsSymptom : Symbol colors don't match expected appearance.
.foregroundStyle but see only one color, you may need .symbolRenderingMode(.palette) or .hierarchical.tint vs .foregroundStyle — In UIKit, tintColor affects Monochrome and Hierarchical. For Palette, use UIImage.SymbolConfiguration(paletteColors:).foregroundStyle from a parent view may override your rendering mode. Apply .symbolRenderingMode() directly on the ImageSymptom : Custom symbol looks too thin or too thick next to text or other symbols.
.font() alignment — The symbol's weight follows the applied font weight. If using .font(.title), ensure your custom symbol has appropriate weight variants.imageScale(.small/.medium/.large) affects overall size. Use .font() for weight matchingSymptom : .symbolEffect(.drawOn) applied to custom symbol but no draw animation occurs.
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.
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."
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.
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)
Weekly Installs
72
Repository
GitHub Stars
610
First Seen
Feb 7, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode67
gemini-cli64
codex64
kimi-cli61
github-copilot61
amp60