利用 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()
}
}