6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

and factoryAdvent Calendar 2018

Day 14

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

Posted at

はじめに

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

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

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

要件

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

iOS9対応したHaptic Feedbackのコード

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

使い方

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

ポイント解説

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

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

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

typealias Action = (() -> Void)

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

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

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

@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とかに書いちゃう事が多いですね。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?