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