You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Refactor MessageHostingView Hit Testing to Improve SwiftUI Touch Handling (#582)
* Refactor `MessageHostingView` hit testing to improve SwiftUI touch handling on iOS 18+ through layer-based detection.
* Moved doc comments to top of hitTest function
Copy file name to clipboardExpand all lines: SwiftMessages/MessageHostingView.swift
+20-11Lines changed: 20 additions & 11 deletions
Original file line number
Diff line number
Diff line change
@@ -51,21 +51,30 @@ public class MessageHostingView<Content>: UIView, Identifiable where Content: Vi
51
51
fatalError("init(coder:) has not been implemented")
52
52
}
53
53
54
+
/// Override hit testing so that only SwiftUI-rendered content inside `MessageHostingView` can receive touches.
55
+
///
56
+
/// Background:
57
+
/// - `MessageHostingView` does not tightly wrap its SwiftUI content, potentially leaving surrounding regions that should not be tappable. There have
58
+
/// been some complications with detecting touches on the SwiftUI content over the years that have led to the current approach:
59
+
/// - On iOS 18, UIKit performs a second hit test that resolves to the `UIHostingController`'s view instead of the actual SwiftUI element.
60
+
/// - On iOS 26, the `UIHostingController`'s view no longer contains any subviews, but its `CALayer` *layer* hierarchy still reflects the SwiftUI content.
61
+
///
62
+
/// All of these issues can be solved by hit testing the layer hierarchy instead of the view hierarchy:
63
+
/// - Call `super.hitTest(point, with: event)` to obtain a candidate view `view`. If our heuristic determines that SwiftUI content was tapped,
64
+
/// then we return `view` to accept the touch. Otherwise, return `nil` to pass the touch through.
65
+
/// - If the candidate is `MessageHostingView` return `nil`.
66
+
/// - If the candidate is directly parented to `MessageHostingView`, this is the `UIHostingController` view containing the SwiftUI content.
67
+
/// To determine if SwiftUI content was touched, we iterate over hosting controller's sublayers and return the candidate if the touch intersects a sublayer.
68
+
/// Otherwise, return `nil`.
69
+
/// - For any other case, we return the candidate because we don't know what's going on.
54
70
publicoverridefunc hitTest(_ point:CGPoint, with event:UIEvent?)->UIView?{
0 commit comments