WWDC Quick Look 💓 By SwiftGGTeam
SF Symbols in SwiftUI

SF Symbols in SwiftUI

观看原视频

Highlight

SwiftUI 在 WWDC21 支持 SF Symbols 的变体和四种渲染模式,开发者可以用 ImageLabelforegroundStylesymbolVariantsymbolRenderingMode 控制符号的语义、尺寸和颜色。

核心内容

一个纸牌 app 里常有很多小图标:红心、黑桃、规则书、个人资料、撤销按钮。它们看起来只是装饰,实际会影响可访问性、动态字体、深色模式和跨平台外观。

过去开发者经常手工挑符号名。iOS 标签栏要用填充版,就把 person.circle.fill 写进代码。macOS 需要轮廓版,又要换成另一个名字。自定义符号还要额外处理 VoiceOver 文本。颜色更麻烦,一个撤销按钮里既要白色箭头,又要红色背景,只靠单色 tint 很难表达。

Apple 在 SF Symbols 3 和 SwiftUI 里补齐了这条链路。系统符号和自定义符号都可以通过 ImageLabel 呈现。符号可以嵌入 Text,跟着文字换行。SwiftUI 组件会自动选择合适的符号变体。开发者也可以用 symbolVariantsymbolRenderingModeforegroundStyle 明确控制整棵视图树。

这让代码回到业务含义。标签栏写基础符号名,平台自己选择填充或轮廓。列表需要填充版,就在外层加一个修饰符。按钮需要多层颜色,就给 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:) 把标题和系统符号组合起来。自定义符号使用资源名称,ImageLabel 都有对应初始化器。

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 引入 ImageLabelVStackView
  • SymbolBasicsView 是一个可直接放进 SwiftUI app 的视图。
  • Image(systemName: "heart") 显示系统提供的红心符号。
  • Label("Heart", systemImage: "heart") 同时提供标题和符号。
  • Image("queen.heart") 从 app 资源里加载自定义符号。
  • Label("Queen of Hearts", image: "queen.heart") 把自定义符号和标题绑定。
  • .padding() 给示例内容留出边距,便于预览。

给只有图片的符号补可访问性文本

02:33Label 会自然提供文本描述。只有 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:14foregroundStyle 控制符号和文本颜色。它可以接收具体颜色,也可以接收语义样式,例如当前 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:41symbolRenderingMode(.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:10symbolRenderingMode(.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:50foregroundStyle 可以接收多个样式。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:00foregroundStyle 接收的是 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 上,统一放大三个符号。

核心启发

  1. 做什么:给设置页或账户页的每一项换成语义化 Label为什么值得做Label 会把标题和符号绑定,SwiftUI 组件可以按上下文显示图标、标题或两者,并给 VoiceOver 提供文本来源。 怎么开始:把 HStack { Image(...); Text(...) } 改成 Label("Profile", systemImage: "person.circle"),再按需要添加 .labelStyle(...)

  2. 做什么:给标签栏和侧边栏统一改用基础符号名。 为什么值得做:SwiftUI 会在标签栏等组件里自动选择合适变体,代码不必手工写 .fill 后缀,也能跨平台适配。 怎么开始:在 tabItem 里使用 Label("Cards", systemImage: "rectangle.portrait.on.rectangle.portrait"),避免直接写 rectangle.portrait.on.rectangle.portrait.fill

  3. 做什么:给分类列表增加多色符号。 为什么值得做symbolRenderingMode(.multicolor) 可以使用 SF Symbols 内置颜色,让健康、天气、纸牌花色这类符号保留自身含义。 怎么开始:在列表外层加 .symbolRenderingMode(.multicolor),用 SF Symbols app 检查目标符号是否支持多色。

  4. 做什么:给复杂工具按钮使用分层模式。 为什么值得做symbolRenderingMode(.hierarchical) 用一个前景样式生成多级透明度,适合牌堆、图层、设备这类含多个视觉层级的图标。 怎么开始:在按钮组外层加 .symbolRenderingMode(.hierarchical),再用 .foregroundStyle(.tint).foregroundStyle(.secondary) 控制整体色系。

  5. 做什么:做一个带背景形状的撤销、删除或确认按钮。 为什么值得做:调色板渲染可以给符号前景和背景分别指定样式,比单色 tint 更接近真实按钮设计。 怎么开始:使用 Image(systemName: "arrow.uturn.backward"),添加 .symbolVariant(.circle.fill),再用 .foregroundStyle(.white, .red).foregroundStyle(.white, .yellow, .red) 指定层级颜色。

关联 Session

评论

GitHub Issues · utterances