SF Symbols in SwiftUI
Swift & UI 入门 13m

SwiftUI 中的 SF Symbols

SF Symbols in SwiftUI

2021年6月11日

在 Apple 官方观看视频

一句话判断

如果你还在用 UIImage + UIImageView 加载 SF Symbols,停下来——SwiftUI 的 Symbol API(特别是 Rendering Modes)用三行代码就能实现以前需要 Core Image 滤镜才能做的效果。

这场 Session 讲了什么

这个 Session 专门讲解如何在 SwiftUI 中使用 SF Symbols,是「Explore the SF Symbols 3 app」(Session 10288)的 SwiftUI 实战篇。核心内容围绕 iOS 15 中新增的 Symbol API 展开:symbolRenderingModeforegroundStyle 的多值传参、以及 symbolVariant 修饰符。

在 iOS 14 中,SwiftUI 的 SF Symbols 支持比较基础——只能用 .renderingMode(.original).renderingMode(.template),对颜色的控制粒度很粗。iOS 15 彻底改变了这个局面:四种渲染模式(Monochrome、Multicolor、Hierarchical、Palette)都有对应的 SwiftUI API,foregroundStyle 可以接受多个颜色参数(分别对应 Symbol 的不同图层),symbolVariant 让你动态切换 Symbol 的变体(比如从 outline 切换到 fill)。

Session 还讲解了 SF Symbols 与 SwiftUI 主题系统的集成:当用户切换 Light/Dark 模式或 Accent Color 时,使用 foregroundStyle(.tint) 的 Symbol 会自动跟随主题变化。

值得深挖的点

foregroundStyle 的多值传参机制。 当你使用 Palette 渲染模式时,foregroundStyle 需要为每个图层指定颜色。SwiftUI 的类型系统巧妙地支持了这个需求——foregroundStyle 接受 variadic 参数(_ styles: S),你可以传一个、两个或三个颜色。第一个颜色对应最上层(前景),第二个对应中间层,第三个对应最下层。比如一个文件夹图标的 Palette 模式:.foregroundStyle(.white, .blue) 会把标签页渲染成白色、文件夹本体渲染成蓝色。这个设计比 UIKit 的 withTintColor(_:renderingMode:) 灵活得多。

symbolVariant 的组合能力。 symbolVariant 修饰符让你可以动态选择 Symbol 的变体,而不需要在代码中硬编码变体名称。比如 .symbolVariant(.fill) 会把 star 变成 star.fill.symbolVariant(.circle.fill) 会把 star 变成 star.circle.fill。这在与按钮状态绑定时特别有用——选中状态用 .fill 变体,未选中状态用默认变体,不需要写 if-else 来切换不同的 systemName。

代码片段

import SwiftUI

// 四种渲染模式的 SwiftUI 用法
struct SymbolModesView: View {
    var body: some View {
        VStack(spacing: 20) {
            // Monochrome:单色,用 foregroundStyle 着色
            Image(systemName: "bell.badge.fill")
                .symbolRenderingMode(.monochrome)
                .foregroundStyle(.blue)

            // Hierarchical:自动分层,前景不透明,背景半透明
            Image(systemName: "bell.badge.fill")
                .symbolRenderingMode(.hierarchical)
                .foregroundStyle(.red)

            // Palette:手动指定每层颜色
            Image(systemName: "bell.badge.fill")
                .symbolRenderingMode(.palette)
                .foregroundStyle(.white, .red)

            // Multicolor:保留系统自带的多色
            Image(systemName: "bell.badge.fill")
                .symbolRenderingMode(.multicolor)
        }
        .font(.largeTitle)
    }
}
// symbolVariant 与按钮状态绑定
struct ToggleButton: View {
    @State private var isFavorite = false

    var body: some View {
        Button {
            isFavorite.toggle()
        } label: {
            Image(systemName: "heart")
                .symbolVariant(isFavorite ? .fill : .none)
                .foregroundStyle(isFavorite ? .red : .gray)
                .font(.title)
                .animation(.easeInOut(duration: 0.2), value: isFavorite)
        }
    }
}
// 配合 Label 和 List 使用 SF Symbols
struct MenuListView: View {
    var body: some View {
        List {
            Label("收件箱", systemImage: "tray.and.arrow.down")
            Label("已发送", systemImage: "paperplane")
            Label("草稿", systemImage: "doc.text")
            Label("垃圾箱", systemImage: "trash")
        }
        // 在 toolbar 中使用 Palette 模式
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button {
                    compose()
                } label: {
                    Image(systemName: "square.and.pencil")
                        .symbolRenderingMode(.palette)
                        .foregroundStyle(.blue, .secondary)
                }
            }
        }
    }
}

最佳实践

  • 优先使用 symbolVariant 而不是手动拼接 systemName 字符串——前者有编译时检查,后者拼错了只有运行时才发现
  • 在 Light/Dark 模式下测试你的 Symbol 配色——foregroundStyle(.tint) 会跟随主题,但硬编码的颜色不会
  • 对于需要动画的 Symbol 切换(比如 heart → heart.fill),使用 .animation(.spring()) 而不是 .animation(.default),弹性动画在图标变形时更自然
  • 用 SF Symbols 3 应用的「Copy SF Symbol Name」功能确保你用的 systemName 拼写正确

还有什么值得关注

  • SwiftUI 的 Label 视图是使用 SF Symbols 的最佳方式——它会自动处理 VoiceOver 标签和布局
  • contentTransition(.symbolEffect) 在后续版本中为 Symbol 添加了内置的过渡动画
  • 自定义 Symbol 的 SVG 模板可以从 SF Symbols 3 应用导出,直接拖进 Asset Catalog 使用
WWDC 2021