【ブブッ】簡単！Haptic Feedback(触覚Feedback)のiOS9対応【ブブッ】

# はじめに

**Haptic Feedback(触覚Feedback)**とは、AppStoreとかでアプリをダウンロードするときにアプリが振動する**アレ**です。
具体的にはFaceIdとかTouchIdとかで認証が成功したら**"ドュティーン"**ってなって、失敗したら"**ドュドュン**"ってなるやつです。
**iOS10**以降かつ**iPhone7**以降で使えるらしい

# iOS9対応しているアプリだけど、iOS10以降の人向けにHaptic Feedbackを使いたい

よくある要望だと思います。

# 要件

- iOS9でもビルドできるコード
- iOS10以降ではHaptic Feedbackが動くが、iOS9以前だったら何も起きない
- OSの条件分岐等はプロダクトコード側にできるだけ入れたくない

# iOS9対応したHaptic Feedbackのコード

```swift
final class FeedbackGeneratorManager {
    
    typealias Action = (() -> Void)
    
    // MARK: - Notification
    
    static func success(with action: @escaping Action) {
        guard #available(iOS 10.0, *) else {
            return action()
        }
        self.doNotification(type: .success, with: action)
    }
    
    static func warning(with action: @escaping Action) {
        guard #available(iOS 10.0, *) else {
            return action()
        }
        self.doNotification(type: .warning, with: action)
    }
    
    static func error(with action: @escaping Action) {
        guard #available(iOS 10.0, *) else {
            return action()
        }
        self.doNotification(type: .error, with: action)
    }
    
    // MARK: - Notification
    
    static func light(with action: @escaping Action) {
        guard #available(iOS 10.0, *) else {
            return action()
        }
        self.doImpact(style: .light, with: action)
    }
    
    static func medium(with action: @escaping Action) {
        guard #available(iOS 10.0, *) else {
            return action()
        }
        self.doImpact(style: .medium, with: action)
    }
    
    static func heavy(with action: @escaping Action) {
        guard #available(iOS 10.0, *) else {
            return action()
        }
        self.doImpact(style: .heavy, with: action)
    }
    
    // MARK: - Selection
    
    static func selection(with action: @escaping Action) {
        guard #available(iOS 10.0, *) else {
            return action()
        }
        self.doSelection(with: action)
    }
}

extension FeedbackGeneratorManager {
    
    @available(iOS 10.0, *)
    private static func doNotification(type: UINotificationFeedbackGenerator.FeedbackType, with action: @escaping Action) {
        let g: UINotificationFeedbackGenerator = UINotificationFeedbackGenerator()
        g.prepare()
        // prepareに数秒待たないといけないので0.1秒待ちます
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            g.notificationOccurred(type)
            action()
        }
    }
    
    @available(iOS 10.0, *)
    private static func doImpact(style: UIImpactFeedbackGenerator.FeedbackStyle, with action: @escaping Action) {
        let g: UIImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: style)
        g.prepare()
        // prepareに数秒待たないといけないので0.1秒待ちます
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            g.impactOccurred()
            action()
        }
    }
    
    @available(iOS 10.0, *)
    private static func doSelection(with action: @escaping Action) {
        let g: UISelectionFeedbackGenerator = UISelectionFeedbackGenerator()
        g.prepare()
        // prepareに数秒待たないといけないので0.1秒待ちます
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            g.selectionChanged()
            action()
        }
    }
}

```

# 使い方

```swift
FeedbackGeneratorManager.success {
    // UIの変化とか
    self.doAnimation()
}
```

# ポイント解説

#### 1. Managerを外から呼ぶ場合に**#availableしたくなかった**ので**以下のguard**を外から呼ばれる**全てのメソッド**で愚直にコピペ

```swift
guard #available(iOS 10.0, *) else {
    return action()
}
```

#### 2. Haptic Feedbackは**UIの動き、変化と一緒に使用**するのが推奨されているため**Closureは必須**にした

```swift
typealias Action = (() -> Void)

static func success(with action: @escaping Action) { /* ... */ }
```

#### 3. FeedbackGeneratorは**prepare後数秒待たないと機能しない**ことがあるらしいが、**0.1秒でも動いたので一旦0.1秒だけ待つ**ようにしている。

※FeedbackGeneratorはprepareしないと動かない

```swift
@available(iOS 10.0, *)
private static func doNotification(type: UINotificationFeedbackGenerator.FeedbackType, with action: @escaping Action) {
    let g: UINotificationFeedbackGenerator = UINotificationFeedbackGenerator()
    g.prepare()
    // prepareに数秒待たないといけないので0.1秒待ちます
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        g.notificationOccurred(type)
        action()
    }
}
```

# 最後に

`#available`周りはできるだけメインのコードには含めないように**隔離**することが大事かなと。
メソッドだったらExtensionとかに書いちゃう事が多いですね。