Highlight
SwiftUI 在 WWDC21 支持 SF Symbols 的变体和四种渲染模式,开发者可以用
Image、Label、foregroundStyle、symbolVariant和symbolRenderingMode控制符号的语义、尺寸和颜色。
核心内容
一个纸牌 app 里常有很多小图标:红心、黑桃、规则书、个人资料、撤销按钮。它们看起来只是装饰,实际会影响可访问性、动态字体、深色模式和跨平台外观。
过去开发者经常手工挑符号名。iOS 标签栏要用填充版,就把 person.circle.fill 写进代码。macOS 需要轮廓版,又要换成另一个名字。自定义符号还要额外处理 VoiceOver 文本。颜色更麻烦,一个撤销按钮里既要白色箭头,又要红色背景,只靠单色 tint 很难表达。
Apple 在 SF Symbols 3 和 SwiftUI 里补齐了这条链路。系统符号和自定义符号都可以通过 Image 与 Label 呈现。符号可以嵌入 Text,跟着文字换行。SwiftUI 组件会自动选择合适的符号变体。开发者也可以用 symbolVariant、symbolRenderingMode 和 foregroundStyle 明确控制整棵视图树。
这让代码回到业务含义。标签栏写基础符号名,平台自己选择填充或轮廓。列表需要填充版,就在外层加一个修饰符。按钮需要多层颜色,就给 foregroundStyle 传入多个样式。符号仍然跟随字体、动态类型、深色模式和平台规则。
可访问性从符号创建开始
Label 把标题和图标绑定在一起。很多 SwiftUI 内置组件会根据上下文显示标题、图标,或两者同时显示。VoiceOver 也能直接使用标题作为文本描述。
只有图片的符号需要额外检查。系统符号可能有默认可访问性描述,但同一个图标在不同 app 里的含义不一定相同。自定义符号还可以通过 Localizable.strings 给图片名称提供本地化文本,SwiftUI 会把它用于可访问性标签。
变体让代码少写平台细节
iOS 标签栏通常使用填充符号。过去要开发者自己挑 .fill 结尾的符号,还要知道某个符号有没有填充版。
WWDC21 的 SwiftUI 会让标签栏等视图自动选择特定变体。开发者使用基础名称,组件在不同平台选择合适外观。如果要在自己的组件里应用同样规则,可以把 symbolVariant(.fill) 放到父视图上。
渲染模式把颜色规则交给符号层级
SF Symbols 支持 monochrome(单色)、multicolor(多色)、hierarchical(分层)和 palette(调色板)四种渲染模式。
单色适合一组图标保持统一颜色。多色使用符号内置颜色,适合纸牌花色这类含义本身带颜色的图标。分层用一个前景样式生成不同透明度,突出符号里的关键部分。调色板允许最多三种样式控制不同层级,适合有前景和背景的按钮。
详细内容
创建系统符号和自定义符号
(00:45)SwiftUI 使用 Image(systemName:) 显示系统符号,使用 Label(_:systemImage:) 把标题和系统符号组合起来。自定义符号使用资源名称,Image 和 Label 都有对应初始化器。
import SwiftUI
struct SymbolBasicsView: View {
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Image(systemName: "heart")
Label("Heart", systemImage: "heart")
Image("queen.heart")
Label("Queen of Hearts", image: "queen.heart")
}
.padding()
}
}
关键点:
import SwiftUI引入Image、Label、VStack和View。SymbolBasicsView是一个可直接放进 SwiftUI app 的视图。Image(systemName: "heart")显示系统提供的红心符号。Label("Heart", systemImage: "heart")同时提供标题和符号。Image("queen.heart")从 app 资源里加载自定义符号。Label("Queen of Hearts", image: "queen.heart")把自定义符号和标题绑定。.padding()给示例内容留出边距,便于预览。
给只有图片的符号补可访问性文本
(02:33)Label 会自然提供文本描述。只有 Image 的场景需要开发者确认 VoiceOver 读出的内容是否符合业务含义。系统符号可以加 accessibilityLabel,自定义符号可以在 Localizable.strings 里给图片名提供本地化文本。
import SwiftUI
struct AccessibleSymbolsView: View {
var body: some View {
VStack(spacing: 12) {
Image(systemName: "heart")
.accessibilityLabel("Ace of Hearts")
Image(systemName: "person.circle")
.accessibilityLabel("Profile")
Image("queen.heart")
}
.padding()
}
}
// Localizable.strings
// "queen.heart" = "Queen of Hearts";
关键点:
Image(systemName: "heart")只显示图标,没有可见标题。.accessibilityLabel("Ace of Hearts")把红心图标描述成当前 app 里的纸牌含义。Image(systemName: "person.circle")使用个人头像符号。.accessibilityLabel("Profile")让 VoiceOver 读出“Profile”。Image("queen.heart")使用自定义符号。Localizable.strings里的"queen.heart"给自定义图片名提供默认可访问性文本。
把符号放进一段文字
(02:59)SwiftUI 允许用字符串插值把 Image(systemName:) 嵌入 Text。这个方式适合让符号跟随文字一起换行,例如联系人列表里的向前箭头。
import SwiftUI
struct InlineSymbolTextView: View {
var body: some View {
Text("""
Thalia, Paul, and
3 others \(Image(systemName: "chevron.forward"))
""")
.font(.body)
.padding()
}
}
关键点:
Text(""" ... """)使用多行字符串展示两行文本。\(Image(systemName: "chevron.forward"))把系统箭头符号插入文字流。.font(.body)让文本和内嵌符号使用 body 文本样式。.padding()让示例在预览中更容易观察换行效果。
用前景样式、字体和缩放调整外观
(03:14)foregroundStyle 控制符号和文本颜色。它可以接收具体颜色,也可以接收语义样式,例如当前 tint 或 secondary。font 同时影响标题和符号尺寸。imageScale 只改变符号相对文字的大小。
import SwiftUI
struct SymbolStylingView: View {
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Label("Heart", systemImage: "heart")
.foregroundStyle(.red)
Label("Heart", systemImage: "heart")
.foregroundStyle(.tint)
Label("Heart", systemImage: "heart")
.foregroundStyle(.secondary)
Label("Heart", systemImage: "heart")
.font(.caption)
Label("Heart", systemImage: "heart")
.imageScale(.large)
}
.padding()
}
}
关键点:
- 第一段
Label使用系统红心符号。 .foregroundStyle(.red)把文本和符号绘制为红色。.foregroundStyle(.tint)使用当前环境的强调色。.foregroundStyle(.secondary)使用次级前景样式。.font(.caption)让文字和符号都使用 caption 文本样式,并跟随动态类型缩放。.imageScale(.large)只改变符号相对文字的比例。
让组件自动或手动选择符号变体
(04:23)SwiftUI 的标签栏会自动选择适合自己的符号变体。开发者在 tabItem 里使用基础符号名,系统会在需要时应用填充等变体。
import SwiftUI
struct CardsTabView: View {
var body: some View {
TabView {
Text("Cards").tabItem {
Label("Cards", systemImage: "rectangle.portrait.on.rectangle.portrait")
}
Text("Rules").tabItem {
Label("Rules", systemImage: "character.book.closed")
}
Text("Profile").tabItem {
Label("Profile", systemImage: "person.circle")
}
Text("Magic").tabItem {
Label("Magic", systemImage: "sparkles")
}
}
}
}
关键点:
TabView创建标签式导航。- 每个
Text是一个标签页内容。 .tabItem定义标签页的标题和符号。Label("Cards", systemImage: ...)使用基础符号名,代码不写.fill后缀。sparkles没有填充版时,系统仍能处理可用变体。
(05:12)自己的列表也可以使用 symbolVariant(.fill)。这个修饰符把变体写入环境,作用于整棵视图层级里的符号。
import SwiftUI
struct FilledCardListView: View {
var body: some View {
List {
Label("Ace of Hearts", systemImage: "suit.heart")
Label("Ace of Spades", systemImage: "suit.spade")
Label("Ace of Diamonds", systemImage: "suit.diamond")
Label("Ace of Clubs", systemImage: "suit.club")
Label("Queen of Hearts", image: "queen.heart")
}
.symbolVariant(.fill)
}
}
关键点:
List展示一组纸牌条目。- 前四个
Label使用系统花色符号。 Label("Queen of Hearts", image: "queen.heart")使用自定义符号。.symbolVariant(.fill)请求列表内符号使用填充变体。- 这个修饰符可以同时影响系统符号和遵循命名规则的自定义符号。
使用多色和分层渲染模式
(06:41)symbolRenderingMode(.multicolor) 使用符号自带的多色定义。适合红心、黑桃、方块、梅花这类颜色直接表达含义的符号。如果某个符号没有多色版本,系统会回退到单色渲染。
import SwiftUI
struct MulticolorCardListView: View {
var body: some View {
List {
Label("Ace of Hearts", systemImage: "suit.heart")
Label("Ace of Spades", systemImage: "suit.spade")
Label("Ace of Diamonds", systemImage: "suit.diamond")
Label("Ace of Clubs", systemImage: "suit.club")
Label("Queen of Hearts", image: "queen.heart")
}
.symbolVariant(.fill)
.symbolRenderingMode(.multicolor)
}
}
关键点:
List仍然使用同一组纸牌标签。.symbolVariant(.fill)先请求填充变体。.symbolRenderingMode(.multicolor)请求使用符号内置颜色。- 没有多色定义的符号会回退到单色模式。
- SF Symbols app 可以检查系统符号是否支持多色,也可以给自定义符号添加多色支持。
(07:10)symbolRenderingMode(.hierarchical) 使用当前前景样式绘制符号,同时给符号内部不同层级应用不同透明度。它适合强调一个复杂图标里的关键部分。
import SwiftUI
struct HierarchicalButtonsView: View {
var body: some View {
HStack(spacing: 16) {
Button(action: {}) {
Image(systemName: "square.3.stack.3d.top.fill")
}
Button(action: {}) {
Image(systemName: "square.3.stack.3d.bottom.fill")
}
}
.symbolRenderingMode(.hierarchical)
.font(.largeTitle)
.padding()
}
}
关键点:
HStack横向排列两个按钮。Button(action: {})定义点击动作,示例里为空实现。- 第一个
Image使用顶部牌堆符号。 - 第二个
Image使用底部牌堆符号。 .symbolRenderingMode(.hierarchical)让两个符号使用分层模式。.font(.largeTitle)放大按钮符号,便于观察层级透明度。
用调色板渲染控制多层颜色
(07:50)foregroundStyle 可以接收多个样式。SwiftUI 会使用调色板渲染模式,把这些样式分配给符号不同层级。演讲中的撤销按钮使用 .circle.fill 变体,再给箭头、圆形背景和第三层分别设置颜色。
import SwiftUI
struct PaletteUndoButtonView: View {
var body: some View {
Button(action: {}) {
Image(systemName: "arrow.uturn.backward")
}
.symbolVariant(.circle.fill)
.foregroundStyle(.white, .yellow, .red)
.font(.largeTitle)
.padding()
}
}
关键点:
Button(action: {})创建一个可点击按钮。Image(systemName: "arrow.uturn.backward")显示撤销箭头。.symbolVariant(.circle.fill)请求圆形填充变体,生成按钮背景。.foregroundStyle(.white, .yellow, .red)提供三个前景样式。- 符号如果只有 primary 和 tertiary 内容,就会使用第一个和第三个样式。
.font(.largeTitle)让符号以较大尺寸显示。
(09:00)foregroundStyle 接收的是 ShapeStyle。除了颜色,也可以使用 .secondary 或 .regularMaterial。这让符号能在模糊背景或材料背景前保持系统视觉效果。
import SwiftUI
struct AdvancedForegroundStylesView: View {
var body: some View {
VStack(spacing: 20) {
Button(action: {}) {
Image(systemName: "arrow.uturn.backward")
}
.symbolVariant(.circle.fill)
.foregroundStyle(.white, .red)
Button(action: {}) {
Image(systemName: "arrow.uturn.backward")
}
.symbolVariant(.circle.fill)
.foregroundStyle(.white, .secondary)
Button(action: {}) {
Image(systemName: "arrow.uturn.backward")
}
.symbolVariant(.circle.fill)
.foregroundStyle(.red, .regularMaterial)
}
.font(.largeTitle)
.padding()
}
}
关键点:
VStack纵向排列三个按钮示例。- 第一个按钮用
.white和.red设置两层颜色。 - 第二个按钮用
.secondary作为第二层样式,适合跟随环境视觉层级。 - 第三个按钮用
.regularMaterial作为第二层样式,可以让符号层级呈现材料效果。 .font(.largeTitle)作用在整个VStack上,统一放大三个符号。
核心启发
-
做什么:给设置页或账户页的每一项换成语义化
Label。 为什么值得做:Label会把标题和符号绑定,SwiftUI 组件可以按上下文显示图标、标题或两者,并给 VoiceOver 提供文本来源。 怎么开始:把HStack { Image(...); Text(...) }改成Label("Profile", systemImage: "person.circle"),再按需要添加.labelStyle(...)。 -
做什么:给标签栏和侧边栏统一改用基础符号名。 为什么值得做:SwiftUI 会在标签栏等组件里自动选择合适变体,代码不必手工写
.fill后缀,也能跨平台适配。 怎么开始:在tabItem里使用Label("Cards", systemImage: "rectangle.portrait.on.rectangle.portrait"),避免直接写rectangle.portrait.on.rectangle.portrait.fill。 -
做什么:给分类列表增加多色符号。 为什么值得做:
symbolRenderingMode(.multicolor)可以使用 SF Symbols 内置颜色,让健康、天气、纸牌花色这类符号保留自身含义。 怎么开始:在列表外层加.symbolRenderingMode(.multicolor),用 SF Symbols app 检查目标符号是否支持多色。 -
做什么:给复杂工具按钮使用分层模式。 为什么值得做:
symbolRenderingMode(.hierarchical)用一个前景样式生成多级透明度,适合牌堆、图层、设备这类含多个视觉层级的图标。 怎么开始:在按钮组外层加.symbolRenderingMode(.hierarchical),再用.foregroundStyle(.tint)或.foregroundStyle(.secondary)控制整体色系。 -
做什么:做一个带背景形状的撤销、删除或确认按钮。 为什么值得做:调色板渲染可以给符号前景和背景分别指定样式,比单色 tint 更接近真实按钮设计。 怎么开始:使用
Image(systemName: "arrow.uturn.backward"),添加.symbolVariant(.circle.fill),再用.foregroundStyle(.white, .red)或.foregroundStyle(.white, .yellow, .red)指定层级颜色。
关联 Session
- What’s new in SF Symbols — 了解 SF Symbols 3 的新符号、颜色渲染模式和系统颜色。
- Create custom symbols — 学习如何让自定义 SVG 符号支持分层、调色板和多色渲染。
- SF Symbols in UIKit and AppKit — 对比 UIKit、AppKit 里配置彩色符号的 API。
- Explore the SF Symbols 3 app — 使用 SF Symbols app 检查符号变体、渲染模式和颜色层级。
- Add rich graphics to your SwiftUI app — 深入 SwiftUI 的 foreground styles、materials 和图形系统。
评论
GitHub Issues · utterances