利用 SwiftUI 的 symbolEffect 實現 SF Symbol 動畫 — iOS 17 新功能

從 iOS 17 開始,只要一行程式即可為 SF Symbol 添加活潑的動畫效果。在研究程式前,我們可以先用 SF Symbols App 認識 SF Symbol 各種生動的動畫效果。

認識 SF Symbol 的動畫效果後,接下來讓我們學習如何在 SwiftUI 實現 SF Symbol 動畫,以下我們以 SF Symbol wifi 為例說明。

import SwiftUI

struct ContentView: View {
var body: some View {
Image(systemName: "wifi")
}
}
  • 永不停止的動畫。
  • 控制動畫的開始和停止。
  • 設定動畫的細節。
  • 設定動畫的速度。
  • 一次性的動畫。
  • 設定動畫的次數。
  • 換圖的 replace 動畫。
  • 出現和消失的動畫。

永不停止的動畫

透過 modifier symbolEffect(_:options:isActive:) 實現 SF Symbol 的動畫效果。

如下圖所示,我們有多種效果可選擇。

  • Appear(出現)。
  • Bounce(彈跳)。
  • Scale(縮放)。
  • Wiggle(擺動,iOS 18 以上)。
  • Rotate(旋轉,iOS 18 以上)。
  • Breathe(呼吸,包含縮放和淡入淡出,iOS 18 以上)。
  • Pulse(脈衝,淡入淡出)。
  • Variable Color(照順序調整不同 layer 的透明度。)
  • Replace(取代,換新的圖案)。
  • Disappear(消失)。

symbolEffect(_:options:isActive:)通常會產生永不停止的動畫,因為 effect 的參數型別是 IndefiniteSymbolEffect,等下我們會再介紹只做一次的動畫寫法。

  • wifi 不斷擺動。
struct ContentView: View {
var body: some View {
Image(systemName: "wifi")
.symbolEffect(.wiggle)
}
}
  • wifi &square.and.arrow.up.circle.fill 同時不斷擺動。

將 symbolEffect 套用在 HStack 時,它也會套用在 HStack 包含的 Image 上。

struct ContentView: View {
var body: some View {
HStack {
Image(systemName: "wifi")
Image(systemName: "square.and.arrow.up.circle.fill")
}
.symbolEffect(.wiggle)
}
}

控制動畫的開始和停止

剛剛的寫法會產生永不停止的動畫,若想手動控製動畫的開始跟停止,可另外透過參數 isActive。

struct ContentView: View {
@State private var isAnimating = false

var body: some View {
VStack(spacing: 10) {
Image(systemName: "wifi")
.symbolEffect(.pulse, isActive: isAnimating)
Button("開始動畫") {
isAnimating = true
}
Button("停止動畫") {
isAnimating = false
}
}

}
}

設定動畫的細節

每種動畫可設定不同的細節,比方 variableColor 可以 iterative 或 cumulative。

  • iterative(反覆)。

依序加深 layer 的透明度,同時間只會有一個 layer 的顏色是深色,其它 layer 是淺色。

struct ContentView: View {
var body: some View {
Image(systemName: "wifi")
.symbolEffect(.variableColor.iterative)
}
}

如下圖所示,一次只有一條 wifi 的線條是深色。

  • cumulative(累積)。

依序加深 layer 的透明度,效果會累積,因此 wifi 深色的線條將從一條變成兩條,然後再變成三條。

struct ContentView: View {
var body: some View {
Image(systemName: "wifi")
.symbolEffect(.variableColor.cumulative)
}
}

設定動畫的速度

參數 options 傳入 .speed(0.1) 可控制動畫的速度,0.1 表示速度變慢,變成原來的 0.1 倍。

struct ContentView: View {
var body: some View {
Image(systemName: "wifi")
.symbolEffect(.pulse, options: .speed(0.1))
}
}

一次性的動畫

透過 modifier symbolEffect(_:options:value:) 實現一次性的動畫,參數 effect 的型別是 DiscreteSymbolEffect,它將透過 value 的改變觸發動畫。

以下程式在按下按鈕時改變 value,觸發 wifi 做一次彈跳動畫。

struct ContentView: View {
@State private var value = 0

var body: some View {
VStack(spacing: 10) {
Image(systemName: "wifi")
.symbolEffect(.bounce, value: value)
Button("開始動畫") {
value += 1
}
}
}
}

設定動畫的次數

參數 options 傳入 .repeat(.periodic(2, delay: 0) ) 可設定動畫的次數,傳入 2 表示做 2 次動畫。

  • 寫法 1。

按下按鈕時做兩次動畫。

struct ContentView: View {
@State private var value = 0

var body: some View {
VStack {
Image(systemName: "wifi")
.symbolEffect(.breathe, options: .repeat(.periodic(2, delay: 0)), value: value)
Button("開始動畫") {
value += 1
}
}
}
}
  • 寫法 2。

畫面出現時做兩次動畫。

struct ContentView: View {
var body: some View {
Image(systemName: "wifi")
.symbolEffect(.breathe, options: .repeat(.periodic(2, delay: 0)))
}
}

換圖的 replace 動畫

呼叫 modifier contentTransition(_:),傳入 .symbolEffect(.replace)

struct ContentView: View {
@State private var isMuted = false

var body: some View {
Button {
isMuted.toggle()
} label: {
Image(systemName: isMuted ? "bell.slash" : "bell")
.contentTransition(.symbolEffect(.replace))
}
}
}

replace 還可以調整細節,設定不同的取代效果。

出現和消失的動畫

以下示範兩種做法。

  • 方法 1: 用 isActive 觸發動畫,元件消失時依然佔著空間,因此畫面排版不受影響。
struct ContentView: View {
@State private var isActive = false

var body: some View {
VStack(spacing: 10) {
Image(systemName: "wifi")
.symbolEffect(.disappear, isActive: isActive)
Button("隱藏") {
isActive = true
}

}
}
}
  • 方法 2: 利用 transition 搭配 .symbolEffect(.disappear) 實現消失動畫,元件消失時不佔空間,因此畫面排版會受影響。
struct ContentView: View {
@State private var isWifiHidden = false

var body: some View {
VStack(spacing: 10) {
if !isWifiHidden {
Image(systemName: "wifi")
.transition(.symbolEffect(.disappear))
}
Button("隱藏") {
isWifiHidden = true
}
}
}
}

參考連結

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com