Skip to content

Commit a70fdac

Browse files
Mehdi Mulanifacebook-github-bot
Mehdi Mulani
authored andcommitted
Measure touch events from nearest "root view"
Summary: This makes RCTTouchHandler follow the same logic when sending touch event coordinates as `UIManager.measure`. This is important as UIManager.measure is used in `Touchable.js` aka the mother of all touch events in RN. In particular, this will make touch events correctly handled for places where RN is embedded into other screens. Reviewed By: shergin Differential Revision: D6838102 fbshipit-source-id: 70cad52606ea931cb637d8aa2d4845818eea0647
1 parent b1cdb7d commit a70fdac

File tree

1 file changed

+28
-1
lines changed

1 file changed

+28
-1
lines changed

React/Base/RCTTouchHandler.m

+28-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ @implementation RCTTouchHandler
3939
NSMutableArray<NSMutableDictionary *> *_reactTouches;
4040
NSMutableArray<UIView *> *_touchViews;
4141

42+
__weak UIView *_cachedRootView;
43+
4244
uint16_t _coalescingKey;
4345
}
4446

@@ -150,7 +152,8 @@ - (void)_updateReactTouchAtIndex:(NSInteger)touchIndex
150152
{
151153
UITouch *nativeTouch = _nativeTouches[touchIndex];
152154
CGPoint windowLocation = [nativeTouch locationInView:nativeTouch.window];
153-
CGPoint rootViewLocation = [nativeTouch.window convertPoint:windowLocation toView:self.view];
155+
RCTAssert(_cachedRootView, @"We were unable to find a root view for the touch");
156+
CGPoint rootViewLocation = [nativeTouch.window convertPoint:windowLocation toView:_cachedRootView];
154157

155158
UIView *touchView = _touchViews[touchIndex];
156159
CGPoint touchViewLocation = [nativeTouch.window convertPoint:windowLocation toView:touchView];
@@ -231,6 +234,26 @@ - (void)_updateAndDispatchTouches:(NSSet<UITouch *> *)touches
231234
[_eventDispatcher sendEvent:event];
232235
}
233236

237+
/***
238+
* To ensure compatibilty when using UIManager.measure and RCTTouchHandler, we have to adopt
239+
* UIManager.measure's behavior in finding a "root view".
240+
* Usually RCTTouchHandler is already attached to a root view but in some cases (e.g. Modal),
241+
* we are instead attached to some RCTView subtree. This is also the case when embedding some RN
242+
* views inside a seperate ViewController not controlled by RN.
243+
* This logic will either find the nearest rootView, or go all the way to the UIWindow.
244+
* While this is not optimal, it is exactly what UIManager.measure does, and what Touchable.js
245+
* relies on.
246+
* We cache it here so that we don't have to repeat it for every touch in the gesture.
247+
*/
248+
- (void)_cacheRootView
249+
{
250+
UIView *rootView = self.view;
251+
while (rootView.superview && ![rootView isReactRootView]) {
252+
rootView = rootView.superview;
253+
}
254+
_cachedRootView = rootView;
255+
}
256+
234257
#pragma mark - Gesture Recognizer Delegate Callbacks
235258

236259
static BOOL RCTAllTouchesAreCancelledOrEnded(NSSet<UITouch *> *touches)
@@ -262,6 +285,8 @@ - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
262285
{
263286
[super touchesBegan:touches withEvent:event];
264287

288+
[self _cacheRootView];
289+
265290
// "start" has to record new touches *before* extracting the event.
266291
// "end"/"cancel" needs to remove the touch *after* extracting the event.
267292
[self _recordNewTouches:touches];
@@ -333,6 +358,8 @@ - (void)reset
333358
[_nativeTouches removeAllObjects];
334359
[_reactTouches removeAllObjects];
335360
[_touchViews removeAllObjects];
361+
362+
_cachedRootView = nil;
336363
}
337364
}
338365

0 commit comments

Comments
 (0)