Commit 629b694
committed
Fix ScrollView contentInset area not responding to scroll gestures on iOS
Implements a runtime swizzling fix for React Native 0.81+ bug where
ScrollView's contentInset area fails to respond to touch events. This
prevents users from initiating scroll gestures by touching the inset
padding area, which is critical for chat UX patterns where contentInset
positions messages at the top of the screen.
## Problem
React Native's RCTScrollViewComponentView.betterHitTest returns the
container view (self) instead of the underlying UIScrollView instance
when the touch is in the contentInset area. This causes touch events
to be intercepted by the container, preventing the scroll view from
receiving gestures.
Upstream issue: facebook/react-native#54123
## Solution
Uses Objective-C runtime method swizzling to override hitTest:withEvent:
on the ScrollView's container view. The fix mirrors the upstream patch:
- Calls the original hitTest:withEvent: implementation
- When the result is self (the container) - which is the bug - dynamically
finds and returns the UIScrollView child instead
- Otherwise preserves the original result (subviews, nil, etc.)
Key implementation detail: the UIScrollView is looked up dynamically at
hit-test time rather than captured during swizzling. This ensures the
fix works across React Native hot reloads and full refreshes where the
view hierarchy is recreated but the swizzled class persists.
Implementation follows the same pattern as PR kirillzyusko#1336's scrollRectToVisible
fix: dynamic subclass creation with idempotent prefix check, applied lazily
in didMoveToWindow.
## Benefits
✅ Users can now scroll by touching contentInset area
✅ Interactive content (Pressables, buttons, inputs) continues to work normally
✅ Survives React Native hot reloads and full refreshes
✅ Fixes chat UX where messages need top positioning via contentInset
✅ Non-breaking: defensive implementation returns early if structure unexpected
✅ Compatible with existing scrollRectToVisible noop fix
✅ Zero-cost for apps not using ClippingScrollView
✅ Provides immediate relief while waiting for upstream React Native fix
## Performance Considerations
The dynamic scrollView lookup only executes when:
- The original hitTest returns the container (empty space/contentInset area)
- NOT when touching actual content (messages, buttons, etc.)
Cost: iterating through container's direct subviews (typically 1-3 views)
- UIScrollView + maybe scroll indicators
- Just pointer comparisons and class checks
- Negligible performance impact
## Risks and Considerations
1 parent 4910c3d commit 629b694
1 file changed
Lines changed: 66 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
86 | 151 | | |
87 | 152 | | |
88 | 153 | | |
| |||
133 | 198 | | |
134 | 199 | | |
135 | 200 | | |
| 201 | + | |
136 | 202 | | |
137 | 203 | | |
138 | 204 | | |
| |||
0 commit comments