👇

【iOS17】ハプティック・フィードバックで心地良い手触り感を演出する

2024/10/02に公開

はじめまして、株式会社TrashXでエンジニアをしているarmticです。iOS17からハプティック・フィードバック(触覚フィードバック)のAPIがSwiftUIに増えたので、実装方法について解説します。

概要

WWDC23のタイミングでiOS17が発表され、SwiftUIの世界でハプティック・フィードバックを返す方法が追加されました。
ViewModifierとして実装されていて、UIKitのAPI( UIFeedbackGenerator )とは使い方が違うのでまとめました。

SwiftUIの世界からハプティック・フィードバックを呼び出すには、.sensoryFeedback系を使用します。

nonisolated
func sensoryFeedback<T>(
    _ feedback: SensoryFeedback,
    trigger: T
) -> some View where T : Equatable

第一引数にはハプティックの種類を決めるSensoryFeedbackを渡し、triggerにはEquatableを渡します。triggerの値に変化があるとハプティックが作動します。

最小構成での実装例

ボタンを押すとcountがインクリメントされ、.selectionフィードバックが返ってきます。

@State var count = 0

var body: some View {
    Button {
        count += 1
    } label: {
        Text("Increment")
    }
    .sensoryFeedback(.selection, trigger: count)
}

ハプティックのオンオフ / 種類の出し分け

  • 「アプリがフォアグラウンド状態に戻ってきた時にフィードバックを返したい」
  • 「条件に応じてフィードバックの種類を出し分けたい」

といったケースのために用意されたAPIも存在します。

条件がtrueの時にフィードバックを返すには

前者の「アプリがフォアグラウンド状態に戻ってきた時にフィードバックを返したい」のように、条件がtrueの時にフィードバックを返したい場合はこのAPIを利用します。

https://developer.apple.com/documentation/swiftui/view/sensoryfeedback(_:trigger:condition:)

nonisolated
func sensoryFeedback<T>(
    _ feedback: SensoryFeedback,
    trigger: T,
    condition: @escaping (T, T) -> Bool
) -> some View where T : Equatable

実装例

@Environment(\.scenePhase) var scenePhase: ScenePhase

var body: some View {
    Text("Hello")
        .sensoryFeedback(.selection, trigger: scenePhase) { _, newValue in
            newValue == .active
        }
}

ScenePhaseにはシーンの状態が入ります。.active, .inactive, .backgroundの3つの値を取る列挙型になっています。

条件に応じてフィードバックの種類を出し分けるには

あらかじめSensoryFeedbackを指定せず、動的にフィードバックの種類を出し分けることもできます。
triggerに指定した値が変化すると、feedbackクロージャで返ってきたフィードバックを再生するAPIです。

https://developer.apple.com/documentation/swiftui/view/sensoryfeedback(trigger:_:)

nonisolated
func sensoryFeedback<T>(
    trigger: T,
    _ feedback: @escaping (T, T) -> SensoryFeedback?
) -> some View where T : Equatable

実装例

TextFieldで名前を入力し、30文字以下というバリデーションを設けるケースを考えます。

nameの値が変更されるとfeedbackのクロージャが呼び出され、name.countに応じて返すフィードバックを出し分け流ことができます。

今回は30文字以上を受け付けないので、それ以上入力しようとすると.errorのフィードバックを返すようにします。

@State var name = ""

var body: some View {
    TextField("your name", text: $name)
        .sensoryFeedback(trigger: name) { _, newValue in
            switch newValue.count {
            case 30...:
                // 30文字以上は受け付けないことを`error`で表現
                return .error
            case 25...:
                // もう直ぐ入力上限に達することを`warning`で表現
                return .warning
            default:
                // フィードバックを返さない
                return nil
            }
        }
}

SensoryFeedbackの種類

sensoryFeedbackに指定できる種類はドキュメントに記載があります。振動の強さや鋭さが違うハプティックを効果的に使うことで印象が変わります。
https://developer.apple.com/documentation/swiftui/sensoryfeedback

Human Interface GuidelinesPlaying Hapticsにも記載がある通り、ハプティックの意味と使い所を理解し、アプリ全体で一貫したハプティックを使うことが重要になります。

XcodeでSensoryFeedbackの定義にジャンプして確認することもできます。

Core Hapticsでさらに複雑なハプティックを設計する

Core Hapticsフレームワークの世界に飛び込んで、さらに複雑なハプティックを設計することも可能です。弊社がつくっているNow Cameraでは、使い心地を追求するために独自のAHAPファイルを定義してハプティックを設計しています。

https://qiita.com/shu223/items/7e1578d7515cb44d3980

事例

いざハプティックを使おう!となった際、実装者によってHIGの解釈がまちまちだと一貫したルールがなく、効果が薄れるかもしれません。チームやプロジェクト内で使い所や意義を共有できていると運用が楽になります。
ハプティックのルールを策定し、運用する方法についての13anさんの記事を紹介します。
https://note.com/13an/n/n98d80471d94b

関連

https://note.com/tdksk/n/nb4498e59dcad

https://zenn.dev/ispec_inc/articles/exploring-and-implementing-haptic-feedback

https://developer.apple.com/videos/play/wwdc2019/810/
https://developer.apple.com/videos/play/wwdc2021/10278

Discussion