SF Symbols 6 使用指南

为了更方便没有 SF Symbols 经验的读者理解,也将往年的 SF Symbols 相关内容归纳整理。本文从 SF Symbols 的特性切入,讨论 SF Symbols 这款由系统字体支持的符号库有哪些特点以及该如何使用。在这次 WWDC 2024 更新中,除了符号的数量增加到了 6000+ 之外,还新增了一些能让符号们更加活泼的动画效果,同时提升整体使用体验,给界面带来了更多活力和想象力,让 SF Symbols 这把利器变得又又又更加趁手和锋利了。

本文基于 WWDC 2024 Session 10188 梳理。

WWDC24History

什么是 SF Symbols

符号在界面中起着非常重要的作用,它们能有效地传达意义,它们可以表明你选择了哪些项目,它们可以用来从视觉上区分不同类型的内容,他们还可以节约空间、整洁界面,而且符号出现在整个视觉系统的各处,这使整个用户界面营造了一种熟悉的感觉。

符号的实现和使用方式多种多样,但设计和使用符号时有一个亘古不变的问题,那就是将符号与用户界面的另一个基本元素——「文本」很好地配合。符号和文字在用户界面中以各种不同的大小被使用,他们之间的排列形式、对齐方式、符号颜色、文本字重与符号粗细的协调、本地化配置以及无障碍设计都需要开发者和设计师来细心配置和协调。

sf-symbols-6-new-animations

为了方便开发者更便捷、轻松地使用符号,Apple 在 iOS 13 中开始引入他们自己设计的海量高质量符号,称之为 SF Symbols。现在 SF Symbols 拥有超过 6000 个符号,是一个图标库,旨在与 Apple 平台的系统字体 San Francisco 无缝集成。每个符号有 9 种字重和 3 种比例,以及四种渲染模式,拥有可变颜色和预设动画的能力,它们的默认设计都与文本标签对齐。同时这些符号是矢量的,这意味着它们是可以被拉伸的,使得他们在无论用什么大小时都会呈现出很好的效果。如果你想基于已有的符号去创造属于你自己的自定义符号,它们也可以被导出并在矢量图形编辑工具中进行编辑以创建新的符号。

对于开发者来说,这套 SF Symbols 无论是在 UIKit,AppKit 还是 SwiftUI 中都能运作良好,且使用方式也很简单方便,寥寥数行代码就可以实现。对于设计师来说,你只需要为符号做三个字重的版本,SF Symbols 就会自动帮你生成其余 9 种字重和 3 种比例的符号,你就制作好了一份可以有预设动画、有四种颜色渲染模式、良好适配整个 Apple 生态的自定义符号。

sf-symbols-6-new-symbols

如何使用 SF Symbols

SF Symbols App

在开始介绍如何使用 SF Symbols 之前,我们可以先下载来自 Apple 官方的 SF Symbols 6 App,这款 App 中支持搜索所有的 SF Symbols,并且记录了每个符号的名称,支持的渲染模式,可变符号的分层预览,不同语言下的变体,不同版本下可能出现的不同的名称。可以实时预览不同渲染模式下不同强调色的不同效果,同时也可以预览每个符号能实现的所有种类的动画效果!你可以在这里下载 SF Symbols 6 App。在今年,更新了许多自动驾驶、动作、本地化相关的符号。

SFSymbolApp

如何使用符号

使用 SF Symbols 非常简单,利用系统框架的 UIImage、NSImage 或者 SwiftUI 中的 Image 即可完成,你所要做的,就是去 SF Symbols App 中找到你喜欢的符号,然后复制这个符号的名字,将它粘贴到代码中即可。对于 SF Symbols 来说,我们可以调节他的字重和比例来满足搭配不同类型文字的需求,和 Apple 平台的系统字体 San Francisco 一样,SF Symbols 有有 9 种字重和 3 种比例来满足你不同场景下的使用需求。

new3x9

Image(systemName: "thermometer.sun.fill")
    .fontWeight(.semibold)
    .imageScale(.large)

符号的渲染模式

通过之前的图片你可能已经注意到了,SF Symbols 可以拥有多种颜色,有一些 symbol 还有预设的配色,例如代表天气、肺部、电池的符号等等。想要使用这些带有自定义颜色的符号,你需要知道:SF Symbols 在渲染模式的颜色逻辑上是预先分层的(如下图的温度计符号就分为三层),我们可以改变渲染模式来调整颜色,SF Symbols 会根据每一层的路径作出相应变化,每个 SF Symbols 都有四种渲染模式

RenderingModes

单色模式 Monochrome

在 iOS 15 / macOS 11 之前,单色模式是唯一的渲染模式,顾名思义,单色模式会让符号有一个单一的颜色。要设置单色模式的符号,我们只需要直接设置视图的 tint 等属性就可以完成。

// SwiftUI
Image(systemName: "battery.100percent.bolt")
    .foregroundStyle(.yellow)

// UIKit
let image = UIImage(systemName: "battery.100percent.bolt")
imageView.image = image
imageView.tintColor = .yellow

Monochrome

分层模式 Hierarchical

从渲染的逻辑上来说每个符号都是预先分层的,如下图所示。符号按顺序最多分成三个层级:Primary,Secondary,Tertiary。SF Symbols 的分层设定不仅在分层模式下有效,在其他渲染模式下也是有意义的

分层模式和单色模式一样,可以设置一个颜色。但是分层模式会以该颜色为基础,生成降低主颜色的不透明度而衍生出来的其他颜色(如下图中的电池符号看起来是由三种黄色组合而成)。在这个模式中,层级结构很重要,如果缺少某一个层级,则相关的派生颜色将不会被使用。

// SwiftUI
Image(systemName: "battery.100percent.bolt")
    .foregroundStyle(.yellow)
    .symbolRenderingMode(.hierarchical)

// UIKit
let image = UIImage(systemName: "battery.100percent.bolt")
imageView.image = image
let config = UIImage.SymbolConfiguration(hierarchicalColor: .yellow)
imageView.preferredSymbolConfiguration = config

Hierarchical

调色盘模式 Palette

调色盘模式和分层模式很像,和分层模式一样是,调色盘模式也会对符号的各个层级进行上色。而不同的是,调色盘模式允许你自由的分别设置各个层级的颜色。

// SwiftUI
Image(systemName: "battery.100percent.bolt")
    .foregroundStyle(.red, .orange, .yellow)
    .symbolRenderingMode(.palette)

// UIKit
let image = UIImage(systemName: "battery.100percent.bolt")
imageView.image = image
let config = UIImage.SymbolConfiguration(paletteColors: [.red, .orange, .yellow])
imgView.preferredSymbolConfiguration = config

Palette

多色模式 Muticolor

在 SF Symbols 中,有许多符号的意象在现实生活中已经深入人心,比如:挂断电话的符号应该是红色的,有关警告的符号应该是黄色的,彩虹符号🌈应该是彩色的,等等。所以 SF Symbols 也提供了与现实世界色彩相契合的颜色模式:多色渲染模式。当你使用多色模式的时候,就能看到预设的橙色太阳符号,红色删除符号,而你不需要指定任何颜色。

// SwiftUI
Image(systemName: "battery.100percent.bolt")
    .symbolRenderingMode(.multicolor)

// UIKit
let image = UIImage(systemName: "battery.100percent.bolt")
imageView.image = image
imgView.preferredSymbolConfiguration = .preferringMulticolor()

Muticolor

自动渲染模式 Automatic

谈论完了四种渲染模式,可以发现每次设置 symbol 的渲染模式其实也是一件费心的事情。为了解决这个问题,每个 symbol 都有一个自动渲染模式,这意味着该符号在代码中使用时,假如你不去特意配置他的渲染模式,那么他将使用默认的渲染模式,例如 shareplay 这个符号将会使用分层模式作为默认渲染模式。

你可以在 SF Symbols App 中查询和预览到所有符号的默认渲染模式是什么。如果你想了解关于更多关于渲染模式的内容,可以看这篇 WWDC 2021 内参:SF Symbols 使用指南

可变颜色

有的时候,符号并不单单代表一个单独的概念或者意象,他也可以代表一些数值、比例或者程度,例如 Wi-Fi 强度或者铃声音量,为了解决这个问题,SF Symbols 引入了可变颜色这个概念。

你可以在 SF Symbol App 中的 Variable 目录中找到所有有可变颜色的符号,平且可以通过右侧面板的滑块来查看不同百分比程度下可变颜色的形态。也许你也注意到了,可变颜色的可变部分实际上也是一种分层的表现,但这里的分层和上文提到的渲染模式使用的分层是不同的。一个符号可以在渲染模式中只分两层,但在可变颜色的分层中分为三层,下图中第二个符号喇叭 speaker.wave.3.fill 就是如此。

在代码中,我们只需要在初始化 symbol 时增加一个 Double 类型的 variableValue 参数,就可以实现可变颜色在不同程度下的不同形态。值得注意的是,假如你的可变颜色(例如上图 Wi-Fi 符号)可变部分有三层,那么这个 variableValue 的判定将会三等分:在 0% 时将不高亮信号,在 0%~33% 时,将高亮一格信号,在 34%~67 % 时,将高亮 2 格信号,在 68% 以上时,将会显示满格信号。值得注意的是,可变颜色的可变部分是利用不透明度来实现的,当可变颜色和不同的渲染模式结合后,也会有很好的效果。

// SwiftUI
Image(systemName: "wifi", variableValue: 0.2)
Image(systemName: "wifi", variableValue: 0.5)
Image(systemName: "wifi", variableValue: 0.8)

// AppKit
let img = NSImage(symbolName: "wifi", variableValue: 0.2)

ColoredVariableSymbols

但仅仅通过 value 来控制 symbol 的可变颜色形态,并不能让我们很方便的展示出如上图这样的动画效果。如果你正在寻找一种能让 symbol 更加活泼的工具,那接下来要介绍的 SF Symbols 动画效果就是你需要的。

认识 Animation

SF Symbols 在这两年的更新中获得了动画的功能,这是一种给你的界面增添活力、动感的方式,你可以从一系列不同的可配置的动画预设中选择你想要的动画来使用。而且最重要的是,这些动画是所有符号通用的,所有的 SF Symbols 在所有的比例字重、渲染模式下,都可以良好的运作动画,这让符号们的动画变的可以非常易用同时可以高度定制化。

Animations

在我们了解怎么让符号们动起来之前,我们要重新了解一下两个概念:

首先,是符号的图层和动画之间的关系:每个符号都有图层结构来定义它,确保符号的图层以正确的顺序排列是很重要的,这有助于决定如何对它们进行颜色处理,同时图层在一个符号的动画效果方面也起着至关重要的作用。默认情况下,一个符号将按层动画化,这意味着,每个层将在某一个时间产生动画,使符号的编排具有清晰和精确的动作。

Layers

接下来,是动画中的空间平面概念,这个空间平面指的是符号在应用运动时用来创造深度感的维度。这些平面是不可见的,但它们帮助我们理解符号如何移动和互动,使动画感觉更有吸引力。让我们这样来想象这些平面:中间平面是位于三维空间中心的平面,这个平面是定位和移动符号的一个参考点;前面的平面是最接近观看者的平面,定位在这个平面上的符号会显得更大;而后面的平面是离观看者最远的,符号在这个平面上会显得比较小。符号根据方向性来使用这些平面。根据动画的类型,符号可以以各种方式在平面内移动,如向上移动,或向下移动,甚至也可以完全离开观众的视线。

Dimension

动画

目前 SF Symbols 的动画一共有 10 种,分别是:AppearDisappearBounceScaleWiggleRotateBreatheVariable ColorPulsReplace

其中,WiggleRotateBreathe 是今年新增的动画,ReplaceVariable Color 在今年也有更新,具体的内容会在后文详细讲解。

接下来我会通过代码来展示每个动画的运作机制和特点,在这之前,我们可以先打开 SF Symbols App,你可以在这里看到所有符号的所有动画的预览,就在每个符号右侧边栏第三个 segment 上,并且你也可以通过调节选项来预览不同的动画效果组合,还可以在右侧直接复制你调整好的动画代码。

在代码中使用动画非常简单,不管是在 SwiftUI 还是在 UIKit、AppKit 中,所有的动画选项都是代码链起来的,不含字符串。所有的动画效果,在 Api 中我们称之为 SymbolEffect

AnimationInApp

Bounce

Bounce 是一个非常有动感的动画,我们常常在强调某个区域或者按钮的时候用到这个效果,Bounce 动画默认会将各个图层单独在不同时间段进行动画,会有一种弹跳的动感,你还可以选择弹跳是向上弹跳还是向下弹跳。

需要注意的是,如果你想要整个符号所有图层同时进行动画,需要在代码中使用 .wholeSymbol 特别指定一下,同理,接下来的要介绍的所有动画(除了 Variable Color 动画)也都是如此,后文就不再单独介绍这个特性了。

// SwiftUI
Image(systemName: "checkmark.seal")
    .symbolEffect(.bounce.up,
                  options: .repeat(.periodic(delay: 1)))
Image(systemName: "checkmark.seal")
    .symbolEffect(.bounce.up.wholeSymbol,
                  options: .repeat(.periodic(delay: 1)))

// UIKit
imgView.addSymbolEffect(.bounce.up)

Bounce

Scale

Scale 在动画效果上和 Bounce 是相似的,但是他更多的是用在提醒用户某个区域正处于一个特殊状态,例如在 macOS 上,我们的鼠标经过了某个按钮,我们可以为该按钮提供一个 Scale 动画并保持住 Scale 状态以提醒用户:这里是可以点击的并且你的指针已经在这个按钮上了。

Image(systemName: "folder.badge.plus")
    .symbolEffect(.scale.up, isActive: isActive)

isActive.toggle()

Scale

Wiggle

Wiggle 是今年新推出的动画,这是一个非常可爱的动画,它可以让符号从以任何角度做一个扭动的效果。我们可以在需要用户特别注意的地方使用这个动画,暗示或提示用户下一步操作的位置。你可以选择:上、下、左、右、顺/逆时针或者用数字指定的角度来掌控扭动的方向。如果你不指定方向,每个符号自身也有一个默认的方向。

Image(systemName: "arrow.right.circle")
    .symbolEffect(.wiggle,
                  options: .repeat(.periodic(delay: 0.5)))
Image(systemName: "iphone.gen3.radiowaves.left.and.right")
    .symbolEffect(.wiggle.clockwise.wholeSymbol,
                  options: .repeat(.periodic(delay: 0.5)))

WiggleAnimation

Rotate

Rotate 也是今年新推出的动画,它可以让符号旋转。这个动画可以运用在等待或者表示在过程中的情况。旋转的方向我们可以选择顺时针、逆时针,也可以通过调整这个动画的 repeat behavior 来实现均匀连续的旋转。

Image(systemName: "fan.desk")
    .symbolEffect(.rotate.clockwise, options: .repeat(.periodic(delay: 0)))
Image(systemName: "fan.desk")
    .symbolEffect(.rotate.counterClockwise, options: .repeat(.continuous))

RotateAnimation

注意,Rotate 在默认的情况下是选择 .byLayer 执行动画,例如在上图的电风扇符号中,只有扇叶层会旋转,这是 SF Symbols 预先设置好的。如果你手动指定动画按照 .wholeSymbol 执行,那整个电风扇符号(连同风扇底座),都会旋转起来,好奇的可以亲手实践一下🐶。

Variable Color

没错!我们也可以为有可变颜色特性的符号做动画,而且动画顺序将会遵循可变颜色的层级顺序来实现,我们系统中的 Wi-Fi 和手机上的信号就用了这种动画。要注意的是,在可变颜色中,我们可以设置部分图层完全不参与可变颜色的变化,那么在可变颜色动画中,这部分图层也完全不会参与动画。另外,为没有可变颜色特性的符号做可变颜色动画时将不会有动画效果。

Variable Color 的动画比较特别,除了可以选择按图层动画或者整体动画之外,他还可以选择动画过程中是否要把图层隐藏、动画模式是累加还是迭代、一次动画结束之后要不要反向再做一次动画等。这些动画都可以在 SF Symbols App 中任意组合预览。

Image(systemName: "rainbow")
    .symbolEffect(.variableColor.cumulative.dimInactiveLayers.reversing)
Image(systemName: "rainbow")
    .symbolEffect(.variableColor.iterative.hideInactiveLayers.nonReversing)

ShowVariableColorOptions

另外在这次更新后,Variable Color 的动画分为 open loop 和 closed loop,对应下图左和右,closed loop 符号的头尾是相连的,现在当 closed loop 的符号执行动画时,动画执行完一圈之后不会有短暂停止,会无缝衔接下一圈动画。后文也会提到在自定义符号时,可以修改符号的 loop 类型。

Image(systemName: "waveform")
    .symbolEffect(.variableColor.iterative, options: .repeat(.continuous))
Image(systemName: "progress.indicator")
    .symbolEffect(.variableColor.iterative, options: .repeat(.continuous))

VariableColorLoopAnimation

Pulse

Pulse 是一种闪烁效果,可以用来表示一种正在进行中的状态,例如我们下左图这个符号可以用来表示正在分享屏幕。要注意的是,和 Rotate 一样,该动画默认来说会按图层执行动画,有的部分符号预设了需要强调闪烁的部分(如下左图),所以在执行动画时,会有部分图层不闪烁。

Image(systemName: "rectangle.inset.filled.and.person.filled")
    .symbolEffect(.pulse)
Image(systemName: "globe")
    .symbolEffect(.pulse)

Pulse

Breathe

Breathe 是一种和 Pulse 有一些类似的动画,更适合用于更活泼、更有生命力的场景。除了像 Pulse 一样会按照图层闪烁之外,Breathe 还会像 Bounce 一样依照图层缓慢移动。你可以手动更改该符号在呼吸时需要不要闪烁。

Image(systemName: "leaf")
    .symbolEffect(.breathe.plain)
Image(systemName: "lungs")
    .symbolEffect(.breathe)

BreatheAnimation

Replace

Replace 是一种替换符号的方案,在你需要给按钮替换或者更新符号时常常用到。他是一种 .contentTransition,你有三种动画 Direction 可以选择:.upUp.offUp.downUp,对应符号淡入淡出的形式。如果你选择的两个符号正好是符合 MagicReplace 要求的,例如下图中的 video.fillvideo.slash.fill ,这两个符号的差别只有一条 slash 线,那么系统会自动使用 MagicReplace 动画,使动画看起来更加流畅。

// SwiftUI
Image(systemName: isActive ? "figure.stand" : "figure.walk")
    .contentTransition(.symbolEffect(.replace.downUp))
Image(systemName: isActive ? "video.fill" : "video.slash.fill")
    .contentTransition(.symbolEffect(.replace)) // MagicReplace

// UIKit
let image = UIImage(systemName: "video.slash.fill")
imageView.setSymbolImage(img, contentTransition: .replace.downUp)

Replace

当你使用的两个符号只有某一部分不同,或者说你使用的两个符号都是从某个基础符号演变而来的时候,你就可以尝试使用 MagicReplace,这个动画不会使原有符号完全消失,而是将两个符号的相同的部分保留,进行平移,而对不同的部分做定制的动画。如果你熟悉 Keynote 的话,这个效果就像 Keynote 里面的神奇移动效果

另外要注意,符号的分层和调色盘渲染模式可能会导致 MagicReplace 失效。

如果你正在使用自定义符号,同时想要使用带有 MagicReplace 的动画,需要从 SF Symbols 6 App 重新导出符号至 Xcode 16

Appear / Disappear

这里我们将 AppearDisappear 放在一起说,他们两个的动画效果很相似,一个是出现,另一个是消失,经常用在符号需要出现和消失的场景中。

Appear&Disappear

但是有点特别的是,这种消失和出现的操作,有两种预期的效果,一种是出现和消失会影响控件的布局,而另一种更像是变透明了,不会影响原有的布局:

2Kinds

这两种效果从逻辑上来说都是合理的,但面对不同的需求我们可能需要选择不同的实现方式来达到效果。我们先来看一下不会影响原有的布局的 Appear / Disappear 是如何实现的:我们通过 .symbolEffect 效果来实现一个符号的出现和消失,他可以由一个值的变动来进行触发,他是一种在自己控件内进行画面更新的操作,不影响这个控件之外的内容。

// SwiftUI
HStack {
    RoundedRectangle(...)
    Image(systemName: "moon.stars")
        .symbolEffect(.disappear, isActive: isMoonHidden)
    Circle(...)
}

// UIKit
imageView.addSymbolEffect(.disappear)
...
imageView.addSymbolEffect(.appear)  // Re-appear

接下来我们来看一下会影响控件的布局的 Appear / Disappear 是如何实现的:你可以发现,其实这种消失和出现的动画,实际上是一种 Transition 的过程,当你执行完 disppear 时,你的控件应当是消失了,比如从屏幕上移除等。

// SwiftUI
HStack {
    Image(systemName: "tree")
    if !isSunHidden {
        Image(systemName: "sun.max.fill")
            .transition(.symbolEffect)
    }
    Image(systemName: "tree")
}

withAnimation {
    isSunHidden.toggle()
}

// UIKit
let imageView = UIImageView()
imageView.addSymbolEffect(.disappear) { context in
    if let imageView = context.sender as? UIImageView, context.isFinished {
        imageView.removeFromSuperview()
    }
}

控制动画

除了产生动画之外,我们也要知道如何控制动画。上文提到的 BounceVariable ColorPulseRotateWiggleBreathe 都属于 Discrete 动画,遵循 DiscreteSymbolEffect 协议。这些动画的触发可以是不连续的、离散的,我们在某个时机告诉系统动画开始进行,动画需要进行几次,这样在动画执行完之后他就不需要我们再去处理了。

// SwiftUI
Image(systemName: "fan.desk")
    .symbolEffect(
        .bounce,
        options: .repeat(2),
        value: value
    )

// UIKit
imageView.addSymbolEffect(.bounce, options: .repeat(.periodic(2)))

那另一种更常见的情况是,我希望在代码中有一个变量来控制动画的状态,使动画可以通过代码来开启或者关闭。我们总结这种动画为 Indefinite 动画,在 iOS 18 中,除了 Replace 之外,所有动画都符合这个要求,遵循 IndefiniteSymbolEffect 协议。

这些动画的特点是当它们执行了之后,他们可能需要停留在运动的状态下一段时间,直到接收到关闭动画的指令。你可以发现很多动画他们既是 Discrete 动画,同时也是 Indefinite 动画。他们既可以单独被触发固定次数,也可以通过变量来控制他们的动画状态,两种行为他们都可以实现。

// SwiftUI
Image(systemName: "fan.desk")
    .symbolEffect(
        .rotate,
        options: .repeat,
        isActive: isActive
    )

// UIKit
imageView.addSymbolEffect(.variableColor)
...
imageView.removeSymbolEffect(ofType: .variableColor)

无论是 Discrete 动画还是 Indefinite 动画,都可以用代码控制动画执行的频率。

// 执行一次
options: .nonRepeating,
// 执行 5 次,期间每次 delay 1 秒
options: .repeat(.periodic(5, delay: 1)),
// 连续执行 linear 动画
options: .repeat(.continuous),
// 调整速度
options: .speed(1),

还记得我们的 AppearDisappear 动画有两种形式吗?上文提到的 Indefinite 动画中的 AppearDisappear 是不影响控件之外、只在控件内部更新画面的那种形式。还有另一种 AppearDisappear 动画,在执行过程中会影响到整体的布局,这种 Appear, Disappear 动画属于 Transition 类型,遵循 TransitionSymbolEffect。还剩下 Repalce 动画,单独属于 Content Transition 类型,遵循 ContentTransitionSymbolEffect

所有的动画遵守的协议,如下图所示。

Protocols6

特别注意,iOS 18 之前 bounce 不支持 Indefinite 协议。

定制属于你的 Symbols

当我们觉得系统提供的 6000+ 个 symbols 不能满足我们需求的时候,我们也可以自己绘制属于自己的 symbol,或是在系统 symbol 的基础上进行修改,导入到 SF Symbols App 中,通过一些简单的调整渲染模式的预设颜色、调整动画的图层等,就可以导出到我们的 app 中使用,更多的定制 symbols 的基础介绍可以在 WWDC 21 内参:定制属于你的 Symbols 中详细了解。

那在今年引入了新动画之后,我们也能够在定制符号的同时,通过调整图层相关信息去微调符号动画的具体表现。比如我们可以在定制符号的同时修改其 RotateWiggle 动画的默认方向、Variable Color 动画的执行方向和 loop 类型。

ModifyDirections

接下来举一个调整自定义符号的 Rotate 动画的例子:

调整自定义符号的 Rotate 动画

我对系统的 thermometer.sun.fill 温度计图标的旋转效果不是很满意,目前的默认动画是整个温度计都在旋转,我想对其进行一些调整。

WholeRotate

我可以打开 SF Symbols App,搜索 thermometer.sun.fill 找到温度计图标,右键选择「复制为一个自定符号」。

Duplicate

在自定符号库中找到我们刚才添加的温度计符号,记得打开画廊浏览模式,在右侧面板中选择第三个 segment,我们可以看到这个符号已经被分成了四个图层,其中三个图层已经合成了一个 Group。

Gallery

仔细观察最下面的那个图层,也就是太阳的部分,你会发现系统隐藏了太阳的部分光点,只露出了 4 颗光点,我们在这里打开这个图层,将太阳的其余光点都点亮。

LightUp

接下来,我们手动标记太阳这一层为「Can Rotate」。

CanRotate

接下来设置动画预设为 Rotate,并且选择 byLayer,点击 Preview,就可以预览到我们新设置的旋转动画了。

AnchorWrong

现在太阳是以整个符号的中心作为锚点进行旋转动画的,这显然是不合适的,我们可以打开右上角的调整锚点和辅助线,将符号的旋转锚点进行拖动调整,同样像这样调整另外两种 scale 下的旋转锚点,就大功告成了。我可以将它导入到我的 app 中,当我对这个新符号执行旋转动画时,默认情况下,旋转的只有太阳,而不是整个符号都旋转。

AdjustAnchor

其他例子

在往年的文章中,也有其他一些定制 symbols 的例子供参考:

扩展

符号变体

对于某些符号来说,有多种表现形式,我们以 heart 这个符号为例子,他有许多种变体:

Variants

如果我们想要使用 heart.circle.fill 这个符号,除了通过字符串拼接名称的方式,我们还可以通过 .symbolVarient 来实现,这种实现方式有助于在代码中统一符号的风格。

Label("Heart", systemImage: "heart")
    .symbolVarient(.circle.fill)

let imageView = UIImageView(image: UIImage(systemName: "heart"))
imageView.imageVariant = .circle.fill

但要知道的是,在很多情况下,显示符号的视图会决定是否使用轮廓或填充,因此你无需指定变体。例如 iOS 标签页栏倾向于采用填充变体,而导航栏则采用轮廓变体,又比如 iOS 标签页倾向于采用填充变体,而 macOS 标签页更倾向于线性符号。

本地化

从 SF Symbols 3 开始,许多符号为特定语言和书写系统提供了很多本地化变体,当设备语言更改时,特定于语言和文字的变体会自动随之变化。

Localized

Symbol Components

SF Symbols 在定制化方面还有一个强大功能功能,那就是 Symbol Components,这个功能可以让你的自定义符号,可以以各种你熟悉的形态加入到你的 app 中,来适配 MacOS、iOS 以及你所需要的界面风格。你通过这个方式生成的新符号,系统会自动地帮你设置好需要 Erase 的图层,同时在所有的动画效果、渲染模式、字重和比例下运作良好,非常方便。同时你需要注意的是,通过这个方式生成的符号,不可以再手动的调整图层以及更改动画设置了。通过这种方式生成的符号,会自动附带 replace 动画下的 MagicReplace 的效果。

Component

翻译符号

在 WWDC 24 中,官方推出了全新的 Translation API 🎉,具体见 Session 10117: Meet the Translation API,同步在 SF Symbols 6 中也新增了 translate 符号以供使用。

translate

总结

从 SF Symbols 的特性和优点我们可以看到,它的出现解决符号与文本之间的协调性问题,在保证了本地化、无障碍化的基础上,Apple 一直在实用性、易用度以及多样性上面给 SF Symbols 加码,拥有 6000+ 的符号的 SF Symbols 能覆盖的应用场景也是越来越多,相信在未来还会有更多新特性、功能给开发者使用。从我个人使用 SF Symbols 的角度来看,真的是越用越顺手了,随着 SF Symbols 的继续发展,我相信对于很多开发者来说,是一个超好用的符号工具🥳。