本文是 WWDC23 内参 的供稿
本文基于 WWDC 2023 Session 10197、Session 10257 和 Session 10258 梳理。为了更方便没有 SF Symbols 经验的读者理解,也将往年的 SF Symbols 相关内容归纳整理。本文从 SF Symbols 的特性切入,讨论 SF Symbols 这款由系统字体支持的符号库有哪些优点以及该如何使用。在这次 WWDC 2023 中,除了符号的数量增加到了 5000+ 之外,还有能让符号们“动”起来的新功能,让 SF Symbols 这把利器变得又又更加趁手和锋利了。
什么是 SF Symbols
符号在界面中起着非常重要的作用,它们能有效地传达意义,它们可以表明你选择了哪些项目,它们可以用来从视觉上区分不同类型的内容,他们还可以节约空间、整洁界面,而且符号出现在整个视觉系统的各处,这使整个用户界面营造了一种熟悉的感觉。
符号的实现和使用方式多种多样,但设计和使用符号时有一个亘古不变的问题,那就是将符号与用户界面的另一个基本元素——「文本」很好地配合。符号和文字在用户界面中以各种不同的大小被使用,他们之间的排列形式、对齐方式、符号颜色、文本字重与符号粗细的协调、本地化配置以及无障碍设计都需要开发者和设计师来细心配置和协调。
为了方便开发者更便捷、轻松地使用符号,Apple 在 iOS 13 中开始引入他们自己设计的海量高质量符号,称之为 SF Symbols。SF Symbols 拥有超过 5000 个符号,是一个图标库,旨在与 Apple 平台的系统字体 San Francisco 无缝集成。每个符号有 9 种字重和 3 种比例,以及四种渲染模式,拥有可变颜色和动画的功能,它们的默认设计都与文本标签对齐。同时这些符号是矢量的,这意味着它们是可以被拉伸的,使得他们在无论用什么大小时都会呈现出很好的效果。如果你想基于已有的符号去创造属于你自己的自定义符号,它们也可以被导出并在矢量图形编辑工具中进行编辑以创建新的符号。
对于开发者来说,这套 SF Symbols 无论是在 UIKit,AppKit 还是 SwiftUI 中都能运作良好,且使用方式也很简单方便,寥寥数行代码就可以实现。对于设计师来说,你只需要为符号做三个字重的版本,SF Symbols 就会自动帮你生成其余 9 种字重和 3 种比例的符号,你就制作好了一份可以有预设动画、有四种颜色渲染模式、良好适配整个 Apple 生态的自定义符号。
如何使用 SF Symbols
SF Symbols App
在开始介绍如何使用 SF Symbols 之前,我们可以先下载来自 Apple 官方的 SF Symbols 5 App,这款 App 中收录了所有的 SF Symbols,并且记录了每个符号的名称,支持的渲染模式,可变符号的分层预览,不同语言下的变体,不同版本下可能出现的不同的名称,可以实时预览不同渲染模式下不同强调色的不同效果,同时也可以预览每个符号能实现的所有种类的动画效果!你可以在这里下载 SF Symbols 5 App。
如何使用符号
使用 SF Symbols 非常简单,利用系统框架的 UIImage、NSImage 或者 SwiftUI 中的 Image 即可完成,你所要做的,就是去 SF Symbols App 中找到你喜欢的符号,然后复制这个符号的名字,将它粘贴到代码中即可。对于 SF Symbols 来说,我们可以调节他的字重和比例来满足搭配不同类型文字的需求,和 Apple 平台的系统字体 San Francisco 一样,SF Symbols 有有 9 种字重和 3 种比例来满足你不同场景下的使用需求。
Image(systemName: "thermometer.sun.fill")
.fontWeight(.semibold)
.imageScale(.large)
符号的渲染模式
通过之前的图片你可能已经注意到了,SF Symbols 可以拥有多种颜色,有一些 symbol 还有预设的配色,例如代表天气、肺部、电池的符号等等。如果要使用这些带有自定义颜色的符号,你需要知道,SF Symbols 在渲染模式的颜色逻辑上是预先分层的(如下图的温度计符号就分为三层),根据每一层的路径,我们可以根据渲染模式来调整颜色,而每个 SF Symbols 都有四种渲染模式。
单色模式 Monochrome
在 iOS 15 / macOS 11 之前,单色模式是唯一的渲染模式,顾名思义,单色模式会让符号有一个单一的颜色。要设置单色模式的符号,我们只需要设置视图的 tint color 等属性就可以完成。
// Monochrome
Image(systemName: "thermometer.sun.fill")
.foregroundStyle(.blue)
分层模式 Hierarchical
每个符号都是预先分层的,如下图所示,符号按顺序最多分成三个层级:Primary,Secondary,Tertiary。SF Symbols 的分层设定不仅在分层模式下有效,在别的渲染模式下也是有作用的。
分层模式和单色模式一样,可以设置一个颜色。但是分层模式会以该颜色为基础,生成降低主颜色的不透明度而衍生出来的其他颜色(如上图中的温度计符号看起来是由三种灰色组合而成)。在这个模式中,层级结构很重要,如果缺少一个层级,相关的派生颜色将不会被使用。
// Hierarchical
Image(systemName: "thermometer.sun.fill")
.foregroundStyle(.gray)
.symbolRenderingMode(.hierarchical)
调色盘模式 Palette
调色盘模式和分层模式很像,但也有些许不同。和分层模式一样是,调色盘模式也会对符号的各个层级进行上色,而不同的是,调色盘模式允许你自由的分别设置各个层级的颜色。
// Palette
Image(systemName: "thermometer.sun.fill")
.foregroundStyle(.gray, .cyan, .teal)
多色模式 Muticolor
在 SF Symbols 中,有许多符号的意象在现实生活中已经深入人心,比如:太阳应该是橙色的,警告应该是黄色的,叶子应该是绿色的的等等。所以 SF Symbols 也提供了与现实世界色彩相契合的颜色模式:多色渲染模式。当你使用多色模式的时候,就能看到预设的橙色太阳符号,红色的闹铃符号,而你不需要指定任何颜色。
// Muticolor
Image(systemName: "thermometer.sun.fill")
.symbolRenderingMode(.multicolor)
自动渲染模式 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)
但仅仅通过 value 来控制 symbol 的可变颜色形态,并不能让我们很方便的展示出如上图这样的动态效果,但是今年推出的 SF Symbols 动画效果完美的解决了这一点,也让可变颜色这个功能变得更加方便实用,我们会在后文中看到详细的动画使用方法。
如果你想了解关于更多关于可变颜色的内容,可以看这篇 WWDC 2022 内参:SF Symbols 4 使用指南。
SF Symbols 新特性:Animation
在 2021 年,SF Symbols 有了不同的渲染模式,让符号们不再是单一的色调;在 2022 年,SF Symbols 获得了可变颜色功能,让符号们可以传达不同强度的状态或者是顺序。但是有没有什么东西可以让使符号更有表现力,让他在功能性、视觉上更有吸引力呢?在这次 WWDC 2023 中,SF Symbols 迎来了一个让人激动的更新:Animation,这是一种给你的界面添加活力、动感的新方式,你可以从一系列不同的可配置的动画预设中选择你想要的动画来使用。而且最重要的是,这些动画是通用的,所有的 SF Symbols 在所有的比例字重、渲染模式下,都可以良好的运作动画,这让符号们的动画变的可以高度定制化。
在我们了解怎么让符号们动起来之前,我们要重新了解一下两个概念:
首先,就是符号的图层和动画之间的关系,每个符号都有图层结构来定义它,确保符号的图层以正确的顺序排列是很重要的,这有助于决定如何对它们进行颜色处理,同时图层在一个符号的动画效果方面也起着至关重要的作用。默认情况下,一个符号将按层动画化,这意味着,每个层将在某一个时间产生动画,使符号的编排具有清晰和精确的动作。
接下来,是动画中的空间平面概念,这个空间平面指的是符号在应用运动时用来创造深度感的维度。这些平面是不可见的,但它们帮助我们理解符号如何移动和互动,使动画感觉更有吸引力。让我们这样来想象这些平面:中间平面是位于三维空间中心的平面,这个平面是定位和移动符号的一个参考点;前面的平面是最接近观看者的平面,定位在这个平面上的符号会显得更大;而后面的平面是离观看者最远的,符号在这个平面上会显得比较小。符号根据方向性来使用这些平面。根据动画的类型,符号可以以各种方式在平面内移动,如向上移动,或向下移动,甚至也可以完全离开观众的视线。
动画
目前 SF Symbols 的动画一共有 7 种,分别是:Appear, Disappear, Bounce, Scale, Variable Color, Pulse, Replace。接下来我会通过代码来展示每个动画的运作机制和特点。在这之前,我们可以先打开 SF Symbols App,这个 App 中有所有符号的所有动画的预览,就在每个符号右侧边栏第三个 tab 上,并且你也可以通过调节选项来预览不同的动画效果组合,并且还可以在右侧直接复制你调整好的动画代码。
在代码中使用动画非常简单,不管是在 SwiftUI 还是在 UIKit、AppKit 中,所有的动画选项都是代码链起来的,不含字符串。所有的动画效果,在 Api 中我们称之为 SymbolEffect
。
Bounce
Bounce 是一个非常有动感的动画,我们常常在强调某个区域或者按钮的时候用到这个效果,Bounce 动画默认会将各个图层单独在不同时间段进行动画,会有一种弹跳的动感,你还可以选择弹跳是向上弹跳还是向下弹跳。
需要注意的是,如果你想要整个符号所有图层同时进行动画,需要在代码中使用 .wholeSymbol
特别指定一下,同理,接下来的要介绍的所有动画(除了 Variable Color 动画)也都是如此,后文就不再单独介绍这个特性了。
// SwiftUI
Image(systemName: "sun.max.fill")
.symbolEffect(.bounce.up)
Image(systemName: "sun.max.fill")
.symbolEffect(.bounce.down.wholeSymbol)
// UIKit
let imgView: UIImageView = ...
imgView.addSymbolEffect(.bounce.up)
Scale
Scale 在动画效果上和 Bounce 是相似的,但是他更多的是用在提醒用户某个区域正处于一个特殊状态,例如在 macOS 上,我们的鼠标经过了某个按钮,我们可以为该按钮提供一个 Scale 动画并保持住 Scale 状态以提醒用户:这里是可以点击的并且你的指针已经在这个按钮上了。
Image(systemName: "folder.badge.plus")
.symbolEffect(.scale.up)
Variable Color
没错!我们也可以为有可变颜色特性的符号做动画,而且动画顺序将会遵循可变颜色的层级顺序来实现,我们系统中的 Wi-Fi 和手机上的信号就用了这种动画。要注意的是,在可变颜色中,我们可以设置部分图层完全不参与可变颜色的变化,那么在可变颜色动画中,这部分图层也完全不会参与动画。另外,为没有可变颜色特性的符号做可变颜色动画时将不会有动画效果。
如果你想知道如何制作带可变颜色特性的自定义符号,请看往年的这篇内参 WWDC 2022 内参:SF Symbols 4 使用指南。
Variable Color 的动画比较特别,除了可以选择按图层动画或者整体动画之外,他还可以选择动画过程中是否要把图层隐藏、动画模式是累加还是迭代、一次动画结束之后要不要反向再做一次动画等。这些动画都可以在 SF Symbols App 中任意组合预览。
Image(systemName: "rainbow")
.symbolEffect(.variableColor.cumulative.dimInactiveLayers.reversing)
Image(systemName: "rainbow")
.symbolEffect(.variableColor.iterative.hideInactiveLayers.nonReversing)
Pulse
Pulse 是一种闪烁效果,可以用来表示一种正在进行中的状态,例如我们下图这个符号可以用来表示正在分享屏幕。要注意的是,该动画默认来说会按图层执行动画,有的部分符号预设了需要强调闪烁的部分(如下图),所以在执行动画时,会有部分图层不闪烁。如果你想自定义哪部分闪烁,可以看后文如何自定义符号。
Image(systemName: "rectangle.inset.filled.and.person.filled")
.symbolEffect(.pulse.byLayer)
Replace
Replace 是一种替换符号的方案,在你需要给按钮替换或者更新符号时常常用到。他是一种 Content Transition,并且你可以选择三种动画 Direction。
// SwiftUI
Image(systemName: dayOrNight ? "sun.rain.fill" : "moon.dust")
.contentTransition(.symbolEffect(.replace.upUp))
// UIKit
let imgView: UIImageView = ...
let img = UIImage(systemName: "moon.dust")
imgView.setSymbolImage(img, contentTransition: .replace.upUp)
Appear / Disappear
这里我们将 Appear 和 Disappear 放在一起说,他们两个的动画效果很相似,一个是出现,另一个是消失,经常用在符号需要出现和消失的场景中。
但是有点特别的是,这种消失和出现的操作,有两种预期的效果,一种是出现和消失会影响控件的布局,而另一种更像是变透明了,不会影响原有的布局:
这两种效果从逻辑上来说都是合理的,但面对不同的需求我们可能需要选择不同的实现方式来达到效果。我们先来看一下不会影响原有的布局的 Appear / Disappear 是如何实现的:我们通过 symbolEffect 效果来实现一个符号的出现和消失,他可以由一个值的变动来进行触发,他是一种在自己控件内进行画面更新的操作,不影响这个控件之外的内容。
// SwiftUI
HStack {
RoundedRectangle(...)
Image(systemName: "moon.stars")
.symbolEffect(.disappear, isActive: isMoonHidden)
Circle(...)
}
// UIKit
let imgView: UIImageView = ...
imgView.addSymbolEffect(.disappear)
...
imgView.addSymbolEffect(.appear) // Re-appear
接下来我们来看一下会影响控件的布局的 Appear / Disappear 是如何实现的:你可以发现,其实这种消失和出现的动画,实际上是一种 Transition 的过程,当你执行完 disppear 时,你的控件应当是消失了,比如从屏幕上移除等。
// SwiftUI
HStack {
RoundedRectangle(...)
if !isMoonHidden {
Image(systemName: "moon.stars")
.transition(.symbolEffect.down)
}
Circle(...)
}
// UIKit
let imgView = UIImageView()
imgView.addSymbolEffect(.disappear) { context in
if let imgView = context.sender as? UIImageView, context.isFinished {
imgView.removeFromSuperview()
}
}
控制动画
除了产生动画之外,我们也要知道如何控制动画。上文提到的 Bounce, Variable Color, Pulse 动画都属于 Discrete
动画,这些动画的触发是不连续的、离散的,我们需要在某个时机告诉他动画开始进行,也可以告诉他动画需要进行几次,但通常来说,动画执行完之后他就不需要我们再去处理了。
// SwiftUI
Image(systemName: "sun.max.fill")
.symbolEffect(
.bounce,
options: .repeat(2)
value: bounceValue
)
// UIKit
let imgView: UIImageView = ...
imgView.addSymbolEffect(.bounce, option: .repeat(2))
那还有一些动画执行完了之后,我们还是要费心去管理一下的,我们总结他们为 indefinite
动画,Appear, Disappear, Scale, Variable Color, Pulse 这些动画都是这个类型的。这些动画的特点是当它们执行了之后,他们可能需要停留在运动的状态下一段时间,直到接收到关闭动画的指令。你可以发现 Variable Color, Pulse 这两个动画他们既是 Discrete
动画,同时也是 indefinite
动画,因为他们既可以单独被触发固定次数,也可以通过变量来控制他们的动画状态,两种行为他们都可以实现。
// SwiftUI
Image(systemName: "sun.max.fill")
.symbolEffect(
.variableColor,
isActive: effectIsActive
)
// UIKit
let imgView: UIImageView = ...
imgView.addSymbolEffect(.variableColor)
...
imgView.removeSymbolEffect(ofType: .variableColor)
还记得我们的 Appear, Disappear 动画有两种形式吗?上文提到的 indefinite
动画中的 Appear, Disappear 是不影响控件之外、只在控件内部更新画面的那种形式。还有另一种 Appear, Disappear 动画,在执行过程中会影响到整体的布局,这种 Appear, Disappear 动画属于 Transition
类型。还剩下 Repalce 动画,单独属于 Content Transition
类型。
我们把动画分成四种类型,是因为目前所有的动画都遵守了这四个类型所代表的协议,如下图所示。也许今后会有更多的动画类型、动画协议加入这个表格,丰富符号们的动画效果,同时我们也许可以通过协议的组合来窥探这些动画的具体实现方式,这部分内容就留给读者自己探索啦😈。
定制属于你的 Symbols
当我们觉得系统提供的 5000 个 symbols 不能满足我们需求的时候,我们也可以自己绘制属于自己的 symbol,或是在系统 symbol 的基础上进行修改,导入到 SF Symbols App 中,通过一些简单的调整渲染模式的预设颜色、调整动画的图层等,就可以导出到我们的 app 中使用,更多的定制 symbols 的基础介绍可以在 WWDC 21 内参:定制属于你的 Symbols 中详细了解。
那在今年引入了动画这个新特性之后,我们能够在定制符号的同时,也可以通过调整图层来定制符号动画的具体表现。
标记需要 Pulse 的图层
在上文的例子中,我们看到了当一个 symbol 在进行 Pulse 动画的时候,是可以有部分图层不参与动画效果,比如 rectangle.inset.filled.and.person.filled
这个符号进行 Pulse 动画的时候,只有中间的屏幕图层在闪烁,边框和人像都不会参加动画。但是当我们在定制一个 symbol 的时候,默认效果是所有图层都是参与 Pulse 闪烁动画的,如下图所示。
这时候,我们可以通过点击每个图层最右侧的 activate always pulse
按钮,也就是那个像小太阳一样的按钮,来激活他的 Pulse 动画,并且一旦你激活了某一个图层,那么剩下的所有未激活图层,都不会参与 Pulse 动画。在这个例子中,我只想我符号中的两颗星星闪烁,那么我需要激活两颗星星所对应图层的小太阳按钮就可以达到效果。
为图层分组
另一个让人苦恼的问题是,默认来说,symbol 将会按照图层的数量和顺序,依次执行每一层的动画,但当我们有比较多的图层的时候,动画会显得很乱,如下图所示。
这时候我们可以点击图层板块左下角的加号,将你想要的图层合并成一个组,这样在执行动画的时候,图层将按组进行动画。例如我想要将星星分为一组,将所有的圆圈分为另一组,下图是我的演示。
Symbol Components
在今年,SF Symbols 在定制化方面还有一个新功能,那就是 Symbol Components,这个功能可以让你的自定义符号,可以以各种你熟悉的形态加入到你的 App 中,来适配 MacOS、iOS 以及你所需要的界面风格。你通过这个方式生成的新符号,系统会自动地帮你设置好 Erase 图层,同时在所有的动画效果、渲染模式、字重和比例下运作良好,非常方便。同时你需要注意的是,通过这个方式生成的符号,不可以再手动的调整图层以及更改动画设置了。
如果你对生成的符号有微调的需求,也可以满足你。我们可以在三个比例下调整这些 components 的细节。
总结
从 SF Symbols 的特性和优点我们可以看到,它的出现解决符号与文本之间的协调性问题,在保证了本地化、无障碍化的基础上,Apple 一直在实用性、易用度以及多样性上面给 SF Symbols 加码,今年新推出的动画比起往年的更新,更加吸引人们的眼球了,目前 SF Symbols 能覆盖的应用场景也是越来越多,目前有了 5000+ 的符号可以使用,相信在未来还会有更多、以及更多的新特性、功能给开发者使用。SF Symbols 在表现能力上,在今年都又获得了巨大的的提升,让人惊艳,随着 SF Symbols 的继续发展,我相信对于很多开发者来说,是一个非常好的符号工具🥳🥳🥳。