Skip to content

Commit

Permalink
Measure touch events from nearest "root view"
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Mehdi Mulani authored and facebook-github-bot committed Jan 31, 2018
1 parent b1cdb7d commit a70fdac
Showing 1 changed file with 28 additions and 1 deletion.
29 changes: 28 additions & 1 deletion React/Base/RCTTouchHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ @implementation RCTTouchHandler
NSMutableArray<NSMutableDictionary *> *_reactTouches;
NSMutableArray<UIView *> *_touchViews;

__weak UIView *_cachedRootView;

uint16_t _coalescingKey;
}

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

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

/***
* To ensure compatibilty when using UIManager.measure and RCTTouchHandler, we have to adopt
* UIManager.measure's behavior in finding a "root view".
* Usually RCTTouchHandler is already attached to a root view but in some cases (e.g. Modal),
* we are instead attached to some RCTView subtree. This is also the case when embedding some RN
* views inside a seperate ViewController not controlled by RN.
* This logic will either find the nearest rootView, or go all the way to the UIWindow.
* While this is not optimal, it is exactly what UIManager.measure does, and what Touchable.js
* relies on.
* We cache it here so that we don't have to repeat it for every touch in the gesture.
*/
- (void)_cacheRootView
{
UIView *rootView = self.view;
while (rootView.superview && ![rootView isReactRootView]) {
rootView = rootView.superview;
}
_cachedRootView = rootView;
}

#pragma mark - Gesture Recognizer Delegate Callbacks

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

[self _cacheRootView];

// "start" has to record new touches *before* extracting the event.
// "end"/"cancel" needs to remove the touch *after* extracting the event.
[self _recordNewTouches:touches];
Expand Down Expand Up @@ -333,6 +358,8 @@ - (void)reset
[_nativeTouches removeAllObjects];
[_reactTouches removeAllObjects];
[_touchViews removeAllObjects];

_cachedRootView = nil;
}
}

Expand Down

0 comments on commit a70fdac

Please sign in to comment.