Skip to content

Commit 5534e8a

Browse files
authored
Fix unbounded intensity cache issue (#671)
1 parent 75dff0e commit 5534e8a

File tree

1 file changed

+44
-4
lines changed

1 file changed

+44
-4
lines changed

Sources/OpenSwiftUI/View/Control/SensoryFeedback/UIKit/UIKitSensoryFeedbackCache.swift

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class AnyUIKitSensoryFeedbackCache {
2626

2727
class UIKitSensoryFeedbackCache<V>: AnyUIKitSensoryFeedbackCache where V: View {
2828
weak var host: _UIHostingView<V>?
29-
var cachedGenerators: [SensoryFeedback.FeedbackType: UIFeedbackGenerator] = [:]
29+
private var cachedGenerators: [GeneratorCacheKey: UIFeedbackGenerator] = [:]
3030

3131
override func implementation(
3232
type: SensoryFeedback.FeedbackType
@@ -59,6 +59,7 @@ class UIKitSensoryFeedbackCache<V>: AnyUIKitSensoryFeedbackCache where V: View {
5959
} createIfNeeded: {
6060
UINotificationFeedbackGenerator()
6161
}
62+
// OpenSwiftUI Addition:
6263
// SwiftUI implementation Bug: introduced since iOS 17 & iOS 26.2 is still not fixed
6364
// FB21332474
6465
case /*.increase, .decrease,*/ .selection:
@@ -108,17 +109,56 @@ class UIKitSensoryFeedbackCache<V>: AnyUIKitSensoryFeedbackCache where V: View {
108109
}
109110
}
110111

112+
/* OpenSwiftUI Addition Begin */
113+
114+
// MARK: - GeneratorCacheKey
115+
116+
/// A cache key that excludes intensity to prevent unbounded cache growth.
117+
///
118+
/// Using `SensoryFeedback.FeedbackType` directly as cache key causes issues
119+
/// because it includes the intensity value. Since intensity is a `Double`,
120+
/// every different intensity creates a new cache entry and generator interaction.
121+
/// The generator style (weight/flexibility) doesn't depend on intensity -
122+
/// intensity is only used at feedback generation time.
123+
private enum GeneratorCacheKey: Hashable {
124+
case success
125+
case warning
126+
case error
127+
case selection
128+
case alignment
129+
case pathComplete
130+
case impactWeight(SensoryFeedback.Weight.Storage)
131+
case impactFlexibility(SensoryFeedback.Flexibility.Storage)
132+
133+
init?(_ type: SensoryFeedback.FeedbackType) {
134+
switch type {
135+
case .success: self = .success
136+
case .warning: self = .warning
137+
case .error: self = .error
138+
case .selection: self = .selection
139+
case .alignment: self = .alignment
140+
case .pathComplete: self = .pathComplete
141+
case let .impactWeight(weight, _): self = .impactWeight(weight)
142+
case let .impactFlexibility(flexibility, _): self = .impactFlexibility(flexibility)
143+
default: return nil
144+
}
145+
}
146+
}
147+
148+
/* OpenSwiftUI Addition End */
149+
111150
private func getGenerator<Generator, Feedback>(
112151
_ type: SensoryFeedback.FeedbackType,
113152
work: (Generator) -> Feedback,
114153
createIfNeeded: () -> Generator
115-
) -> Feedback where Generator: UIFeedbackGenerator, Feedback: LocationBasedSensoryFeedback {
154+
) -> Feedback? where Generator: UIFeedbackGenerator, Feedback: LocationBasedSensoryFeedback {
155+
guard let cacheKey = GeneratorCacheKey(type) else { return nil }
116156
let generator: Generator
117-
if let cachedGenerator = cachedGenerators[type] {
157+
if let cachedGenerator = cachedGenerators[cacheKey] {
118158
generator = cachedGenerator as! Generator
119159
} else {
120160
generator = createIfNeeded()
121-
cachedGenerators[type] = generator
161+
cachedGenerators[cacheKey] = generator
122162
host!.addInteraction(generator)
123163
}
124164
return work(generator)

0 commit comments

Comments
 (0)