更新於 2023 年 02 月 03 日
# 提供聲音和觸覺回饋(Haptic)
相信大家都有經歷過,自己點了一個按鈕,但是畫面完全沒有變化,你心裡想:現在是我沒有按到?還是正在跑?我應該要重按一次嗎?會不會打斷什麼?
這樣的使用者體驗是令人失望的,設計 app 的時候,提供明確的互動回饋是很重要的。最基本的、一定要有的就是視覺回饋的,也就是畫面的變化。
除此之外,我們還可以再對重要的場合加上聲音和觸覺的回饋,經典的例子就是 Apple Pay 付款的觸覺跟聽覺回饋。
這篇文章就介紹簡單介紹一些加入這兩種回饋的方式。
# 設計之前的考量
在開始介紹之前,我想提醒大家,這兩種回饋都不是絕對能產生的,使用者可能關閉音效或觸覺回饋、也可能他的裝置不支援觸覺回饋,所以你還是需要做好視覺回饋。
再來是要掌握使用的時機,這兩種回饋多了就會變成干擾。Human Interface Guidelines 中針對這兩項都有提供建議,設計前建議閱讀:
播放聲音
觸覺回饋
# AudioToolbox
內建音效
如果你需要的只是使用內建音效,那你只需要這個 framework。import 之後使用 AudioServicesPlaySystemSound
就可以播放內建音效。
// 播放通知音效
AudioServicesPlaySystemSound(SystemSoundID(1007))
這裡唯一的問題就是,這個魔法的 ID 1007 從哪裡來?
答案:直接到這個 repo 下方的 readme 找。
自訂音效
除此之外,這個 SystemSoundID 其實是透過 URL 建立的,所以你也可以放自己的音效。
func registerSoundID(filename: String, ext: String) -> SystemSoundID {
var systemID = SystemSoundID.zero
// 取得檔案 URL,CFURL 是可以直接 bridge 的。
let url = Bundle.main.url(forResource: filename, withExtension: ext)! as CFURL
// ID 會用被寫入 systemID 的位置
let result = AudioServicesCreateSystemSoundID(url, &systemID)
// 確保沒有錯誤
guard result == noErr else { return .zero }
return systemID
}
// 假設要播放檔案 success.mp3
let id = registerSoundID(filename: "success", ext: "mp3")
AudioServicesPlayAlertSound(id)
注意事項
注意這個 framework 是用來處理簡單、立即執行的短音效,彈性較低,下面是它的一些限制。
- 使用 30 秒以內的音效。
- 以系統音量播放,無法調整。
- 只能馬上播放。
- 無法循環、從音樂中間開始播放。
- 無法指定輸出裝置。
看起來限制很多,但用來播放簡單的音效回饋已經足夠,如果你需要更多功能的話就要使用 AVFoundation 這個 framework 了。這個 framework 提供的功能非常豐富,網路上的資料也很多,這邊就不特別介紹。
# UIFeedbackGenerator
想要提供簡單的觸覺回饋,UIKit 中的 UIFeedbackGenerator
是最簡單的方式。
UIKit 提供了三種實作的類型:
UIImpactFeedbackGenerator
:適用可量化的反應,像是重力、壓力變化等等,你可以設定它的強度。UISelectionFeedbackGenerator
:提供選擇區域內容變化的觸覺回饋。UINotificationFeedbackGenerator
:提供成功、錯誤、警告的觸覺回饋。
這些差別要自己實際體驗才比較好理解,建議寫個簡單專案,先進手機按看看。
以下示範使用 UINotificationFeedbackGenerator。
let generator = UINotificationFeedbackGenerator()
// 成功的觸覺回饋。
generator.notificationOccurred(.success)
用 prepare 降低延遲
UIFeedbackGenerator 使用上非常簡單,創好實例、呼叫方法就可以。不過有一個方法,可以讓這個觸覺回饋的延遲更低,建議在「可能發生觸覺回饋」的前幾秒先呼叫 prepare()
這個方法。
例如:在某個按鈕出現在畫面時呼叫 prepare,這樣在使用者點下按鈕時就可以更快收到觸覺回饋。
以下是幾個關於 prepare()
的使用須知:
- 被呼叫後,觸覺回饋的引擎將進入「準備完畢(prepared)」的狀態,幾秒後沒有事件發生的話就會變回「待機(idle)」狀態。
- 你可以重複呼叫多次 prepared,這並不會進行重複準備、產生效能問題,而是單純延長它「準備完畢」狀態的時間。
# Core Haptics
如果你需要更加客製化的觸覺回饋,那你需要的是 Core Haptics 這個 framework。你可以定義觸覺回饋的 pattern,重複循環使用。在設計遊戲或音樂相關的 app 的時候,你可能會用到它。
我自己並沒有深入玩過它,如果你有興趣的話,可以參考 Paul Hudson 的這篇教學。
# 將聲音和觸覺一起管理
這兩種回饋常常會是一起使用的,所以我個人喜歡把它們放在一起管理。
以下是簡單地在 SwiftUI 中實作內建音效 + 通知觸覺回饋的示範。
import UIKit
import AudioToolbox
// 內建音效
enum SystemSound: Int {
case click = 1104
case error = 1053
case messageSent = 1055
case notificationDefault = 1007
}
extension SystemSound {
func play() {
AudioServicesPlaySystemSound(SystemSoundID(rawValue))
}
}
// 呼叫的方法
extension UINotificationFeedbackGenerator {
func play(sound: SystemSound, haptic: UINotificationFeedbackGenerator.FeedbackType) {
sound.play()
notificationOccurred(haptic)
}
}
// 放到環境變數中,因為本身的變化不需要觀察,也不需要多個實例。
struct FeedbackGeneratorKey: EnvironmentKey {
static var defaultValue = UINotificationFeedbackGenerator()
}
extension EnvironmentValues {
var feedbackGenerator: UINotificationFeedbackGenerator {
get { self[FeedbackGeneratorKey.self] }
set { self[FeedbackGeneratorKey.self] = newValue }
}
}
在 SwiftUI 中呼叫的情況
import SwiftUI
struct ContentView: View {
// 從環境變數中拿到共用的實例
@Environment(\.feedbackGenerator) var feedbackGenerator
var body: some View {
Button("Error Feedback") {
feedbackGenerator.play(sound: .error, haptic: .error)
// 如果有可能繼續被點擊,就可以播放後直接 prepare。
feedbackGenerator.prepare()
}
.onAppear { feedbackGenerator.prepare() }
}
}
# 結語
在 app 之中,適當的使用音效和觸覺會讓使用者體驗大大加分,這篇文章主要介紹的 AudioToolbox 和 UIFeedbackGenerator 使用上也都很簡單,試著幫你的 app 新增看看吧 😊