利用 UIKit 的 addSymbolEffect 實現 SF Symbol 動畫 — iOS 17 新功能

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

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

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.

let wifiImageView = UIImageView(image: UIImage(systemName: "wifi"))
view.addSubview(wifiImageView)
wifiImageView.translatesAutoresizingMaskIntoConstraints = false
wifiImageView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
wifiImageView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor).isActive = true
}
}


#Preview {
ViewController()
}
  • 利用 addSymbolEffect 加入 SF Symbol 動畫。
  • 設定動畫的細節。
  • 設定動畫的速度。
  • 永不停止的動畫。
  • 設定動畫的次數。
  • 換圖的 replace 動畫。
  • 出現和消失的動畫。
  • 移除動畫。

利用 addSymbolEffect 加入 SF Symbol 動畫

image view 可呼叫 addSymbolEffect 添加動畫效果。

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

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

每種動畫預設執行的次數不同,比方 wiggle 預設將執行一次,因此畫面出現時 wifi 將左右搖擺一次。

wifiImageView.addSymbolEffect(.wiggle)

pulse 預設會無限循環,因此 wifi 將不斷地淡入淡出。

wifiImageView.addSymbolEffect(.pulse)

設定動畫的細節

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

  • iterative(反覆)。

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

wifiImageView.addSymbolEffect(.variableColor.iterative)

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

  • cumulative(累積)。

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

wifiImageView.addSymbolEffect(.variableColor.cumulative)

設定動畫的速度

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

wifiImageView.addSymbolEffect(.pulse, options: .speed(0.1))

永不停止的動畫

  • 連續的動畫,中間不停頓。

參數 options 傳入 .repeat(.continuous)

wifiImageView.addSymbolEffect(.rotate, options: .repeat(.continuous))
  • 每完成一次動畫,稍做停頓再做下一次動畫。

參數 options 傳入 .repeat(.periodic(delay: 0)),delay 設定停頓的秒數,就算設定 0 也會有一點點的停頓。

wifiImageView.addSymbolEffect(.rotate, options: .repeat(.periodic(delay: 0)))

設定動畫的次數

periodic 可設定次數,以下程式的 wifi 將旋轉兩次。

wifiImageView.addSymbolEffect(.rotate, options: .repeat(.periodic(2, delay: 0)))

換圖的 replace 動畫

呼叫 setSymbolImage,contentTransition 傳入 .replace。

bellImageView.setSymbolImage(UIImage(systemName: "bell.slash")!, contentTransition: .replace)

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

出現和消失的動畫

呼叫 addSymbolEffect 時傳入 .appear 或 .disappear。

以下例子點擊畫面時星星將消失。

class ViewController: UIViewController {
let wifiImageView = UIImageView(image: UIImage(systemName: "wifi"))

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.

view.addSubview(wifiImageView)
wifiImageView.translatesAutoresizingMaskIntoConstraints = false
wifiImageView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
wifiImageView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor).isActive = true
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
wifiImageView.addSymbolEffect(.disappear)

}
}

addSymbolEffect 也有動畫完成時觸發的參數 completion,以下程式設定當 wifiImageView 消失時呼叫 removeFromSuperview 將它移除。

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
wifiImageView.addSymbolEffect(.disappear) { context in
if let imageView = context.sender as? UIImageView,
context.isFinished {
imageView.removeFromSuperview()
}
}
}

移除動畫

一般只做一次的動畫不需要另外移除,因為它做完後會自動移除。不過如果是無限循環的動畫效果,需要中止動畫時可呼叫 removeSymbolEffect 或 removeAllSymbolEffects。

範例: 點擊畫面時移除 wifi 不斷淡入淡出的 pulse 動畫。

class ViewController: UIViewController {
let wifiImageView = UIImageView(image: UIImage(systemName: "wifi"))

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.

view.addSubview(wifiImageView)
wifiImageView.translatesAutoresizingMaskIntoConstraints = false
wifiImageView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
wifiImageView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor).isActive = true

wifiImageView.addSymbolEffect(.pulse)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

wifiImageView.removeAllSymbolEffects()
}
}

參考連結

--

--

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

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