Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class AnyUIKitSensoryFeedbackCache {

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

override func implementation(
type: SensoryFeedback.FeedbackType
Expand Down Expand Up @@ -59,6 +59,7 @@ class UIKitSensoryFeedbackCache<V>: 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:
Expand Down Expand Up @@ -108,17 +109,56 @@ class UIKitSensoryFeedbackCache<V>: 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<Generator, Feedback>(
_ 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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

host is weak, so host! can crash on a cache miss if this cache outlives the hosting view. Consider returning nil (or otherwise no-op) when host is nil before calling addInteraction(_:).

🤖 Was this useful? React with 👍 or 👎

}
return work(generator)
Expand Down
Loading