diff --git a/Sources/OpenSwiftUI/View/Control/SensoryFeedback/UIKit/UIKitSensoryFeedbackCache.swift b/Sources/OpenSwiftUI/View/Control/SensoryFeedback/UIKit/UIKitSensoryFeedbackCache.swift index a2730e19f..c9fa46ae7 100644 --- a/Sources/OpenSwiftUI/View/Control/SensoryFeedback/UIKit/UIKitSensoryFeedbackCache.swift +++ b/Sources/OpenSwiftUI/View/Control/SensoryFeedback/UIKit/UIKitSensoryFeedbackCache.swift @@ -26,7 +26,7 @@ class AnyUIKitSensoryFeedbackCache { class UIKitSensoryFeedbackCache: AnyUIKitSensoryFeedbackCache where V: View { weak var host: _UIHostingView? - var cachedGenerators: [SensoryFeedback.FeedbackType: UIFeedbackGenerator] = [:] + private var cachedGenerators: [GeneratorCacheKey: UIFeedbackGenerator] = [:] override func implementation( type: SensoryFeedback.FeedbackType @@ -59,6 +59,7 @@ class UIKitSensoryFeedbackCache: AnyUIKitSensoryFeedbackCache where V: View { } createIfNeeded: { UINotificationFeedbackGenerator() } + // OpenSwiftUI Addition: // SwiftUI implementation Bug: introduced since iOS 17 & iOS 26.2 is still not fixed // FB21332474 case /*.increase, .decrease,*/ .selection: @@ -108,17 +109,56 @@ class UIKitSensoryFeedbackCache: AnyUIKitSensoryFeedbackCache where V: View { } } + /* OpenSwiftUI Addition Begin */ + + // MARK: - GeneratorCacheKey + + /// A cache key that excludes intensity to prevent unbounded cache growth. + /// + /// Using `SensoryFeedback.FeedbackType` directly as cache key causes issues + /// because it includes the intensity value. Since intensity is a `Double`, + /// every different intensity creates a new cache entry and generator interaction. + /// The generator style (weight/flexibility) doesn't depend on intensity - + /// intensity is only used at feedback generation time. + private enum GeneratorCacheKey: Hashable { + case success + case warning + case error + case selection + case alignment + case pathComplete + case impactWeight(SensoryFeedback.Weight.Storage) + case impactFlexibility(SensoryFeedback.Flexibility.Storage) + + init?(_ type: SensoryFeedback.FeedbackType) { + switch type { + case .success: self = .success + case .warning: self = .warning + case .error: self = .error + case .selection: self = .selection + case .alignment: self = .alignment + case .pathComplete: self = .pathComplete + case let .impactWeight(weight, _): self = .impactWeight(weight) + case let .impactFlexibility(flexibility, _): self = .impactFlexibility(flexibility) + default: return nil + } + } + } + + /* OpenSwiftUI Addition End */ + private func getGenerator( _ type: SensoryFeedback.FeedbackType, work: (Generator) -> Feedback, createIfNeeded: () -> Generator - ) -> Feedback where Generator: UIFeedbackGenerator, Feedback: LocationBasedSensoryFeedback { + ) -> Feedback? where Generator: UIFeedbackGenerator, Feedback: LocationBasedSensoryFeedback { + guard let cacheKey = GeneratorCacheKey(type) else { return nil } let generator: Generator - if let cachedGenerator = cachedGenerators[type] { + if let cachedGenerator = cachedGenerators[cacheKey] { generator = cachedGenerator as! Generator } else { generator = createIfNeeded() - cachedGenerators[type] = generator + cachedGenerators[cacheKey] = generator host!.addInteraction(generator) } return work(generator)