From 89f12315b23193971b215069785fecc9bbf99222 Mon Sep 17 00:00:00 2001 From: magnuslo Date: Sun, 21 Jun 2026 12:52:43 +0200 Subject: [PATCH] perf(ios): reuse and pre-prepare feedback generators to reduce impact latency impact() and notification() created a new UIImpactFeedbackGenerator / UINotificationFeedbackGenerator on every call and never called prepare(), forcing the Taptic Engine to cold-start each time (perceptible latency on the first fire). Cache one generator per impact style plus the notification generator, prepare() them on creation, and re-prepare() after each event so the next trigger stays warm. Mirrors the existing prepared-generator pattern already used by selectionStart()/selectionChanged(). No public API change. --- ios/Sources/HapticsPlugin/Haptics.swift | 29 ++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/ios/Sources/HapticsPlugin/Haptics.swift b/ios/Sources/HapticsPlugin/Haptics.swift index 97ad97d..96d47fd 100644 --- a/ios/Sources/HapticsPlugin/Haptics.swift +++ b/ios/Sources/HapticsPlugin/Haptics.swift @@ -6,14 +6,37 @@ import CoreHaptics var selectionFeedbackGenerator: UISelectionFeedbackGenerator? + // Reuse and keep the feedback generators in a prepared state. Apple's docs + // recommend calling prepare() ahead of time so the Taptic Engine does not + // cold-start on the first impact, which otherwise adds perceptible latency. + // Generators are lazily created (and prepared) on first use and re-prepared + // after every event so the next trigger stays warm. + private lazy var impactGenerators: [UIImpactFeedbackGenerator.FeedbackStyle: UIImpactFeedbackGenerator] = [:] + private lazy var notificationGenerator: UINotificationFeedbackGenerator = { + let generator = UINotificationFeedbackGenerator() + generator.prepare() + return generator + }() + + private func impactGenerator(for style: UIImpactFeedbackGenerator.FeedbackStyle) -> UIImpactFeedbackGenerator { + if let generator = impactGenerators[style] { + return generator + } + let generator = UIImpactFeedbackGenerator(style: style) + generator.prepare() + impactGenerators[style] = generator + return generator + } + @objc public func impact(_ impactStyle: UIImpactFeedbackGenerator.FeedbackStyle) { - let generator = UIImpactFeedbackGenerator(style: impactStyle) + let generator = impactGenerator(for: impactStyle) generator.impactOccurred() + generator.prepare() } @objc public func notification(_ notificationType: UINotificationFeedbackGenerator.FeedbackType) { - let generator = UINotificationFeedbackGenerator() - generator.notificationOccurred(notificationType) + notificationGenerator.notificationOccurred(notificationType) + notificationGenerator.prepare() } @objc public func selectionStart() {