Created
June 21, 2022 18:40
-
-
Save Jiropole/51a62b7f8dbfa115128203203a4268b2 to your computer and use it in GitHub Desktop.
A convenient wrapper exposing the full range of Taptic Engine functions. Helps with the guesswork around timing and energy conservation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Foundation | |
| import UIKit | |
| import Combine | |
| /// Wraps the Taptic Engine patterns. | |
| /// For most accurate timing, call `prepare()` within a short interval before `trigger()`. | |
| /// If more than 5 seconds pass after either call, the engine is released to conserve energy. | |
| class HapticImpact { | |
| /// Specifies feedback type, used with the `prepare()` function. | |
| enum ImpactType { | |
| // uses UISelectionFeedbackGenerator | |
| case selectionChanged | |
| // uses UINotificationFeedbackGenerator | |
| case success | |
| case warning | |
| case error | |
| // uses UIImpactFeedbackGenerator | |
| case heavy | |
| case light | |
| case medium | |
| case rigid | |
| case soft | |
| case custom(intensity: CGFloat) | |
| } | |
| private var generator: UIFeedbackGenerator? | |
| private var impactType: ImpactType = .light | |
| private var expiryCancellable: AnyCancellable? | |
| /// Should be called slightly prior to triggering, to avoid any delay starting the Taptic engine. Once prepared, | |
| /// the haptic engine remains engaged for 5 seconds. If no `trigger()`, occurs within that time, the engine | |
| /// is released to conserve energy. | |
| func prepare(impactType: ImpactType) { | |
| switch impactType { | |
| case .selectionChanged: | |
| generator = UISelectionFeedbackGenerator() | |
| case .success, .warning, .error: | |
| generator = UINotificationFeedbackGenerator() | |
| default: | |
| generator = UIImpactFeedbackGenerator(style: impactType.feedbackType) | |
| } | |
| generator?.prepare() | |
| self.impactType = impactType | |
| restartTimeout() | |
| } | |
| /// Trigger the feedback. Ideally `trigger()` is called at some short delay after `prepare()`. Once triggered, | |
| /// the haptic engine remains engaged for 5 seconds. If no additional `trigger()`, occurs within that time, the engine | |
| /// is released to conserve energy. | |
| func trigger() { | |
| if generator == nil { | |
| prepare(impactType: impactType) | |
| } | |
| switch impactType { | |
| case .selectionChanged: | |
| (generator as? UISelectionFeedbackGenerator)?.selectionChanged() | |
| case .success, .warning, .error: | |
| (generator as? UINotificationFeedbackGenerator)?.notificationOccurred(impactType.notificationType) | |
| case .custom(let intensity): | |
| (generator as? UIImpactFeedbackGenerator)?.impactOccurred(intensity: intensity) | |
| default: | |
| (generator as? UIImpactFeedbackGenerator)?.impactOccurred() | |
| } | |
| restartTimeout() | |
| } | |
| } | |
| private extension HapticImpact { | |
| private func restartTimeout() { | |
| expiryCancellable = Timer.publish(every: 5.0, on: RunLoop.main, in: .tracking) | |
| .autoconnect() | |
| .sink { [weak self] _ in | |
| self?.generator = nil | |
| self?.expiryCancellable?.cancel() | |
| } | |
| } | |
| } | |
| private extension HapticImpact.ImpactType { | |
| var notificationType: UINotificationFeedbackGenerator.FeedbackType { | |
| switch self { | |
| case .success: | |
| return UINotificationFeedbackGenerator.FeedbackType.success | |
| case .warning: | |
| return UINotificationFeedbackGenerator.FeedbackType.warning | |
| default: | |
| return UINotificationFeedbackGenerator.FeedbackType.error | |
| } | |
| } | |
| var feedbackType: UIImpactFeedbackGenerator.FeedbackStyle { | |
| switch self { | |
| case .heavy: | |
| return .heavy | |
| case .light: | |
| return .light | |
| case .medium: | |
| return .medium | |
| case .rigid: | |
| return .rigid | |
| default: | |
| return .soft | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment