UIFeedbackGeneratorの使い方と便利に使えるライブラリ


> この記事は[VASILY DEVELOPERS BLOG](http://tech.vasily.jp/entry/ios_ui_feedbcak_generator)にも同じ内容で投稿しています。よろしければ他の記事もご覧ください。

今回は開発中のアプリで使った `UIFeedbackGenerator` についてご紹介します。

`UIFeedbackGenerator`とは、iOS 10以降で利用できるHaptic Feedback (触覚フィードバック) のAPIです。

<br />

# この記事の要約
- 一般的な`UIFeedbackGenerator`の使い方を紹介。
- iOS Human Interface Guideline でどのように推奨されているか解説。
- 自分はこんな場面で導入してみました。
- `UIFeedbackGenerator`によるフィードバックを簡単に実装できる[ライブラリ](https://github.com/WorldDownTown/TapticEngine)を公開しました。

<br />

# Haptic Feedback とは
「触覚フィードバック」と訳します（今後 "Haptic Feedback" と呼びます）。
iPhone 6s 以降に搭載された Taptic Engineというハードウェアによる振動で、ユーザーのアクションに対するフィードバックを表現します。

## Haptic Feedback の例
- iPhone 7のホームボタンを押した時の振動
- ホーム画面のアプリアイコンを強めに押してQuick Actionsが開く時の振動
- `UISlider` のスライダーが両端にぶつかった時の小さな振動
- `UISwitch` の ON / OFF を切り替えた時の小さな振動

<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20170410/20170410205015.png" width="300px" alt="quick_actions" /> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20170410/20170410210230.png" width="300px" alt="uislider" />

<br />

# UIFeedbackGeneratorの使い方
表題の`UIFeedbackGenerator`は抽象クラスで、Haptic Feedbackを発生させる実クラスは [UIImpactFeedbackGenerator](https://developer.apple.com/reference/uikit/uiimpactfeedbackgenerator), [UISelectionFeedbackGenerator](https://developer.apple.com/reference/uikit/uiselectionfeedbackgenerator), [UINotificationFeedbackGenerator](https://developer.apple.com/reference/uikit/uinotificationfeedbackgenerator) の3つです。
下記の表にそれぞれ特徴をまとめました。

|`UIFeedbackGenerator`のサブクラス|種類|用途|
|:--|:--|:--|
|`UIImpactFeedbackGenerator`|3種類<br />`UIImpactFeedbackStyle` (`.light`, `.medium`, `.heavy`)|UIの衝突やスナップを表現します|
|`UISelectionFeedbackGenerator`|1種類のみ|一連の離散値を動きを伝えます|
|`UINotificationFeedbackGenerator`|3種類<br />`UINotificationFeedbackType` (`.success`, `.warning`, `.error`)|タスクやアクションの成功/警告/失敗を通知する|


## 実装方法

例) `UIImpactFeedbackGenerator`

```swift
class ViewController: UIViewController {

    private let feedbackGenerator: Any? = {
        if #available(iOS 10.0, *) {
            let generator: UIImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
            generator.prepare()
            return generator
        } else {
            return nil
        }
    }()

    @IBAction private func light() {
        if #available(iOS 10.0, *), let generator = feedbackGenerator as? UIImpactFeedbackGenerator {
            generator.impactOccurred()
        }
    }
```

- この先のiOS Human Interface Guidelineでも説明していますが、Haptic Feedbackの実行は遅延することがあるため、Haptic Feedbackと同時に音声を流すときなど、実行前の適切なタイミングで`UIFeedbackGenerator` の [prepare()](https://developer.apple.com/reference/uikit/uifeedbackgenerator/2369818-prepare) メソッドを呼ぶことが推奨されています。
    - `prepare()` を呼んでおくことで数秒間準備状態になり、Haptic Feedback発生のレイテンシーを下げることができます。
    - `prepare()`のタイミングが早すぎたり、Haptic Feedback発生と同時に呼んだ場合は効果がありません。
    - `prepare()`メソッドを呼ぶことはオプションですが、[ドキュメント](https://developer.apple.com/reference/uikit/uifeedbackgenerator/2369818-prepare#discussion)では強く推奨されています。
- iOS 10以降のAPIのため、iOS 9以下のOSをサポートする際は、`UImpactFeedbackGenerator`のプロパティを生成するタイミングと、Haptic Feedbackを発生させるタイミングで、iOSのバージョン判別が必要になります。


<br />

# Haptic Feedbackの使いどころ
[iOS Human Interface Guidelines](https://developer.apple.com/ios/human-interface-guidelines/interaction/feedback/#haptics) に Haptic Feedback を実装する上での注意点がまとめられています。
下記はそのガイドラインの意訳と解説です。

- ここでいうフィードバックとは、アクションの結果をユーザーに提示する手段です。
- アラート表現はフィードバック方法としては強力ですが、重要な情報を含まないアラートを多用していると、そのうち重要な情報も含めてアラートそのものを無視するようになります。
- そこでHaptic Feedbackを使いましょう。
ユーザーに注意喚起してアクションを強化する物理的フィードバック(振動)を発生させます。

### Success.

入金成功や車の解錠成功を伝えます。

### Use haptics judiciously.

フィードバック全体の意味が薄れてしまうので、多用は避けましょう。

### In general, provide haptic feedback in response to user-initiated actions.

ユーザーのアクションに合わせてフィードバックを発生させましょう。任意のタイミングでのフィードバックは意識を中断させ、誤解を生みます。

### Don't redefine feedback types.

フィードバックタイプを意図どおりに使用してください。タスクが成功したことをユーザーに通知するには、 `UINotificationFeedbackGenerator` の `UINotificationFeedbackType.success` を使用し、それ以外のものは使わないようにしましょう。

### Fine tune your visual experience for haptics.

視覚と触覚のフィードバックを同時に発生させることでアクションと結果のより深いつながりを提供します。

**例)**
操作のエラーを表現する時に音と一緒にHaptic Feedbackを発生させる。

### Don’t rely on a single mode of communication.

全ての端末がHaptic Feedbackをサポートしているわけではないので、アクションの結果をHaptic Feedbackだけにしてはいけません。

### Use haptics when visual feedback may be occluded.

オブジェクトをスクリーン上の場所にドラッグするときなどは、指で隠れてしまいます。特定の場所や値に到達したときにユーザーにHaptic Feedbackで通知することを検討してください。

### Prepare the system before initiating feedback.

Haptic Feedbackは遅延が伴うことがあります。フィードバックを発生させる直前にシステムを準備することが最善です。そうしないと、Haptic Feedbackが遅すぎて、ユーザーの行動や画面に表示されている内容から切り離されているように感じる可能性があります。

### Synchronize haptics with accompanying sound.

Haptic Feedbackは音を発生させません。Haptic Feedbackと同時に音が必要なら、各自で音声同期処理を実装する必要があります。


<br />

# こんな場面で導入してみました
公開しているライブラリ [RangeSeekSlider](https://github.com/WorldDownTown/RangeSeekSlider) の、段階的に値を変えられるスライダーで、値が変わったことを伝えるためにHaptic Feedbackを発生させるようにしました。（前の項目では **Use haptics when visual feedback may be occluded.** に当たるものです。）
値が変わったタイミングで `UISelectionFeedbackGenerator` のフィードバックを呼び出しています。

下記のGIFの「Range with Step」のオレンジ色のスライダーです。
![20170411124552.gif](https://qiita-image-store.s3.amazonaws.com/0/11454/d95f8215-5a35-fc5f-3d2b-8f6d3f90ae76.gif)


<br />

# ライブラリ化しました
- [TapticEngine](https://github.com/WorldDownTown/TapticEngine) と言うライブラリにして公開しました。
- [150行程度](https://github.com/WorldDownTown/TapticEngine/blob/master/Sources/TapticEngine/TapticEngine.swift)の小さいライブラリです。
- まだシェアが多く切ることができないiOS 9と同居して使えるようにしました。
    - OS判定の必要がありません。
    - Taptic Engine が搭載されていない端末、iOS 9端末では処理を呼び出しても何も動作しません。

```swift
// Triggers a impact feedback between small, light user interface elements. (`UIImpactFeedbackStyle.light`)
TapticEngine.impact.feedback(.light)

// Triggers a impact feedback between moderately sized user interface elements. (`UIImpactFeedbackStyle.medium`)
TapticEngine.impact.feedback(.medium)

// Triggers a impact feedback between large, heavy user interface elements.  (`UIImpactFeedbackStyle.heavy`)
TapticEngine.impact.feedback(.heavy)

// Triggers a selection feedback to communicate movement through a series of discrete values.
TapticEngine.selection.feedback()

// Triggers a notification feedback, indicating that a task has completed successfully. (`UINotificationFeedbackType.success`)
TapticEngine.notification.feedback(.success)

// Triggers a notification feedback, indicating that a task has produced a warning. (`UINotificationFeedbackType.warning`)
TapticEngine.notification.feedback(.warning)

// Triggers a notification feedback, indicating that a task has failed. (`UINotificationFeedbackType.error`)
TapticEngine.notification.feedback(.error)

// Prepare a impact feedback for `UIImpactFeedbackStyle.light`.
TapticEngine.impact.prepare(.light)

// Prepare a selection feedback.
TapticEngine.selection.prepare()

// Prepare a notification feedback.
TapticEngine.notification.prepare()
```


<br />

# 最後に
今回は、`UIFeedbackGenerator` の使い方について紹介しました。公開したライブラリは小さなライブラリなので、気になることがあれば是非ソースコードを読んでPRを送ってください。