Fabric: Introducing RCTSurfaceTouchHandler
Summary: RCTSurfaceTouchHandler is a complete rewrite of RCTTouchHandler which uses direct Fabric-specific event dispatching pipeline and several new approaches to managing active events (such as high-performant C++ collections, better management of identifier pool, and so on). Besides that, the new implementation is much more W3C compliant that it used to be (see old TODOs near `receiveTouches()` implementation in Javascript). So, touch events work now! Reviewed By: fkgozali Differential Revision: D8246713 fbshipit-source-id: 218dc15cd8f982237de7e2497ff36a7bfe6d37cc
This commit is contained in:
parent
d01290dd34
commit
a32be38017
|
@ -67,4 +67,9 @@ using namespace facebook::react;
|
|||
return YES;
|
||||
}
|
||||
|
||||
- (SharedEventHandlers)touchEventHandlers
|
||||
{
|
||||
return _eventHandlers;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTSurfaceTouchHandler : UIGestureRecognizer
|
||||
|
||||
- (void)attachToView:(UIView *)view;
|
||||
- (void)detachFromView:(UIView *)view;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,360 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTSurfaceTouchHandler.h"
|
||||
|
||||
#import <UIKit/UIGestureRecognizerSubclass.h>
|
||||
#import <fabric/view/ViewEventHandlers.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/RCTViewComponentView.h>
|
||||
|
||||
#import "RCTConversions.h"
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
template <size_t size>
|
||||
class IdentifierPool {
|
||||
public:
|
||||
|
||||
void enqueue(int index) {
|
||||
usage[index] = false;
|
||||
}
|
||||
|
||||
int dequeue() {
|
||||
while (true) {
|
||||
if (!usage[lastIndex]) {
|
||||
usage[lastIndex] = true;
|
||||
return lastIndex;
|
||||
}
|
||||
lastIndex = (lastIndex + 1) % size;
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
for (int i = 0; i < size; i++) {
|
||||
usage[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool usage[size];
|
||||
int lastIndex;
|
||||
};
|
||||
|
||||
@protocol RCTTouchableComponentViewProtocol <NSObject>
|
||||
- (SharedViewEventHandlers)touchEventHandlers;
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
RCTTouchEventTypeTouchStart,
|
||||
RCTTouchEventTypeTouchMove,
|
||||
RCTTouchEventTypeTouchEnd,
|
||||
RCTTouchEventTypeTouchCancel,
|
||||
};
|
||||
|
||||
struct ActiveTouch {
|
||||
Touch touch;
|
||||
SharedViewEventHandlers eventHandlers;
|
||||
|
||||
struct Hasher {
|
||||
size_t operator()(const ActiveTouch &activeTouch) const {
|
||||
return std::hash<decltype(activeTouch.touch.identifier)>()(activeTouch.touch.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
struct Comparator {
|
||||
bool operator()(const ActiveTouch &lhs, const ActiveTouch &rhs) const {
|
||||
return lhs.touch.identifier == rhs.touch.identifier;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
static void UpdateActiveTouchWithUITouch(ActiveTouch &activeTouch, UITouch *uiTouch, UIView *rootComponentView) {
|
||||
CGPoint offsetPoint = [uiTouch locationInView:uiTouch.view];
|
||||
CGPoint screenPoint = [uiTouch locationInView:uiTouch.window];
|
||||
CGPoint pagePoint = [uiTouch locationInView:rootComponentView];
|
||||
|
||||
activeTouch.touch.offsetPoint = RCTPointFromCGPoint(offsetPoint);
|
||||
activeTouch.touch.screenPoint = RCTPointFromCGPoint(screenPoint);
|
||||
activeTouch.touch.pagePoint = RCTPointFromCGPoint(pagePoint);
|
||||
|
||||
activeTouch.touch.timestamp = uiTouch.timestamp;
|
||||
|
||||
if (RCTForceTouchAvailable()) {
|
||||
activeTouch.touch.force = uiTouch.force / uiTouch.maximumPossibleForce;
|
||||
}
|
||||
}
|
||||
|
||||
static ActiveTouch CreateTouchWithUITouch(UITouch *uiTouch, UIView *rootComponentView) {
|
||||
UIView *componentView = uiTouch.view;
|
||||
|
||||
ActiveTouch activeTouch = {};
|
||||
|
||||
if ([componentView respondsToSelector:@selector(touchEventHandlers)]) {
|
||||
activeTouch.eventHandlers = [(id<RCTTouchableComponentViewProtocol>)componentView touchEventHandlers];
|
||||
activeTouch.touch.target = (Tag)componentView.tag;
|
||||
}
|
||||
|
||||
UpdateActiveTouchWithUITouch(activeTouch, uiTouch, rootComponentView);
|
||||
return activeTouch;
|
||||
}
|
||||
|
||||
static BOOL AllTouchesAreCancelledOrEnded(NSSet<UITouch *> *touches) {
|
||||
for (UITouch *touch in touches) {
|
||||
if (touch.phase == UITouchPhaseBegan ||
|
||||
touch.phase == UITouchPhaseMoved ||
|
||||
touch.phase == UITouchPhaseStationary) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
static BOOL AnyTouchesChanged(NSSet<UITouch *> *touches) {
|
||||
for (UITouch *touch in touches) {
|
||||
if (touch.phase == UITouchPhaseBegan ||
|
||||
touch.phase == UITouchPhaseMoved) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Surprisingly, `__unsafe_unretained id` pointers are not regular pointers
|
||||
* and `std::hash<>` cannot hash them.
|
||||
* This is quite trivial but decent implementation of hasher function
|
||||
* inspired by this research: https://stackoverflow.com/a/21062520/496389.
|
||||
*/
|
||||
template<typename PointerT>
|
||||
struct PointerHasher {
|
||||
constexpr std::size_t operator()(const PointerT &value) const {
|
||||
return reinterpret_cast<size_t>(&value);
|
||||
}
|
||||
};
|
||||
|
||||
@interface RCTSurfaceTouchHandler () <UIGestureRecognizerDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTSurfaceTouchHandler {
|
||||
std::unordered_map<
|
||||
__unsafe_unretained UITouch *,
|
||||
ActiveTouch,
|
||||
PointerHasher<__unsafe_unretained UITouch *>
|
||||
> _activeTouches;
|
||||
|
||||
UIView *_rootComponentView;
|
||||
IdentifierPool<11> _identifierPool;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super initWithTarget:nil action:nil]) {
|
||||
// `cancelsTouchesInView` and `delaysTouches*` are needed in order
|
||||
// to be used as a top level event delegated recognizer.
|
||||
// Otherwise, lower-level components not built using React Native,
|
||||
// will fail to recognize gestures.
|
||||
self.cancelsTouchesInView = NO;
|
||||
self.delaysTouchesBegan = NO; // This is default value.
|
||||
self.delaysTouchesEnded = NO;
|
||||
|
||||
self.delegate = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithTarget:(id)target action:(SEL)action)
|
||||
|
||||
- (void)attachToView:(UIView *)view
|
||||
{
|
||||
RCTAssert(self.view == nil, @"RCTTouchHandler already has attached view.");
|
||||
|
||||
[view addGestureRecognizer:self];
|
||||
_rootComponentView = view;
|
||||
}
|
||||
|
||||
- (void)detachFromView:(UIView *)view
|
||||
{
|
||||
RCTAssertParam(view);
|
||||
RCTAssert(self.view == view, @"RCTTouchHandler attached to another view.");
|
||||
|
||||
[view removeGestureRecognizer:self];
|
||||
_rootComponentView = nil;
|
||||
}
|
||||
|
||||
- (void)_registerTouches:(NSSet<UITouch *> *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
auto &&activeTouch = CreateTouchWithUITouch(touch, _rootComponentView);
|
||||
activeTouch.touch.identifier = _identifierPool.dequeue();
|
||||
_activeTouches.emplace(touch, activeTouch);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updateTouches:(NSSet<UITouch *> *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
UpdateActiveTouchWithUITouch(_activeTouches[touch], touch, _rootComponentView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_unregisterTouches:(NSSet<UITouch *> *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
auto &&activeTouch = _activeTouches[touch];
|
||||
_identifierPool.enqueue(activeTouch.touch.identifier);
|
||||
_activeTouches.erase(touch);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_dispatchTouches:(NSSet<UITouch *> *)touches eventType:(RCTTouchEventType)eventType
|
||||
{
|
||||
TouchEvent event = {};
|
||||
std::unordered_set<ActiveTouch, ActiveTouch::Hasher, ActiveTouch::Comparator> changedActiveTouches = {};
|
||||
std::unordered_set<SharedViewEventHandlers> uniqueEventHandlers = {};
|
||||
BOOL isEndishEventType = eventType == RCTTouchEventTypeTouchEnd || eventType == RCTTouchEventTypeTouchCancel;
|
||||
|
||||
for (UITouch *touch in touches) {
|
||||
auto &&activeTouch = _activeTouches[touch];
|
||||
|
||||
if (!activeTouch.eventHandlers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
changedActiveTouches.insert(activeTouch);
|
||||
event.changedTouches.insert(activeTouch.touch);
|
||||
uniqueEventHandlers.insert(activeTouch.eventHandlers);
|
||||
}
|
||||
|
||||
for (auto &&pair : _activeTouches) {
|
||||
if (!pair.second.eventHandlers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
isEndishEventType &&
|
||||
event.changedTouches.find(pair.second.touch) != event.changedTouches.end()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
event.touches.insert(pair.second.touch);
|
||||
}
|
||||
|
||||
for (auto &&eventHandlers : uniqueEventHandlers) {
|
||||
event.targetTouches.clear();
|
||||
|
||||
for (auto &&pair : _activeTouches) {
|
||||
if (pair.second.eventHandlers == eventHandlers) {
|
||||
event.targetTouches.insert(pair.second.touch);
|
||||
}
|
||||
}
|
||||
|
||||
switch (eventType) {
|
||||
case RCTTouchEventTypeTouchStart:
|
||||
eventHandlers->onTouchStart(event);
|
||||
break;
|
||||
case RCTTouchEventTypeTouchMove:
|
||||
eventHandlers->onTouchMove(event);
|
||||
break;
|
||||
case RCTTouchEventTypeTouchEnd:
|
||||
eventHandlers->onTouchEnd(event);
|
||||
break;
|
||||
case RCTTouchEventTypeTouchCancel:
|
||||
eventHandlers->onTouchCancel(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - `UIResponder`-ish touch-delivery methods
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
|
||||
[self _registerTouches:touches];
|
||||
[self _dispatchTouches:touches eventType:RCTTouchEventTypeTouchStart];
|
||||
|
||||
if (self.state == UIGestureRecognizerStatePossible) {
|
||||
self.state = UIGestureRecognizerStateBegan;
|
||||
} else if (self.state == UIGestureRecognizerStateBegan) {
|
||||
self.state = UIGestureRecognizerStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesMoved:touches withEvent:event];
|
||||
|
||||
[self _updateTouches:touches];
|
||||
[self _dispatchTouches:touches eventType:RCTTouchEventTypeTouchMove];
|
||||
|
||||
self.state = UIGestureRecognizerStateChanged;
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesEnded:touches withEvent:event];
|
||||
|
||||
[self _updateTouches:touches];
|
||||
[self _dispatchTouches:touches eventType:RCTTouchEventTypeTouchEnd];
|
||||
[self _unregisterTouches:touches];
|
||||
|
||||
if (AllTouchesAreCancelledOrEnded(event.allTouches)) {
|
||||
self.state = UIGestureRecognizerStateEnded;
|
||||
} else if (AnyTouchesChanged(event.allTouches)) {
|
||||
self.state = UIGestureRecognizerStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesCancelled:touches withEvent:event];
|
||||
|
||||
[self _updateTouches:touches];
|
||||
[self _dispatchTouches:touches eventType:RCTTouchEventTypeTouchCancel];
|
||||
[self _unregisterTouches:touches];
|
||||
|
||||
if (AllTouchesAreCancelledOrEnded(event.allTouches)) {
|
||||
self.state = UIGestureRecognizerStateCancelled;
|
||||
} else if (AnyTouchesChanged(event.allTouches)) {
|
||||
self.state = UIGestureRecognizerStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
// Technically, `_activeTouches` must be already empty at this point,
|
||||
// but just to be sure, we clear it explicitly.
|
||||
_activeTouches.clear();
|
||||
_identifierPool.reset();
|
||||
}
|
||||
|
||||
- (BOOL)canPreventGestureRecognizer:(__unused UIGestureRecognizer *)preventedGestureRecognizer
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
|
||||
{
|
||||
// We fail in favour of other external gesture recognizers.
|
||||
// iOS will ask `delegate`'s opinion about this gesture recognizer little bit later.
|
||||
return ![preventingGestureRecognizer.view isDescendantOfView:self.view];
|
||||
}
|
||||
|
||||
#pragma mark - UIGestureRecognizerDelegate
|
||||
|
||||
- (BOOL)gestureRecognizer:(__unused UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
||||
{
|
||||
// Same condition for `failure of` as for `be prevented by`.
|
||||
return [self canBePreventedByGestureRecognizer:otherGestureRecognizer];
|
||||
}
|
||||
|
||||
@end
|
|
@ -17,7 +17,7 @@
|
|||
#import <React/RCTSurfaceDelegate.h>
|
||||
#import <React/RCTSurfaceRootView.h>
|
||||
#import <React/RCTSurfaceView.h>
|
||||
#import <React/RCTTouchHandler.h>
|
||||
#import <React/RCTSurfaceTouchHandler.h>
|
||||
#import <React/RCTUIManagerUtils.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
|
@ -39,7 +39,7 @@
|
|||
|
||||
// The Main thread only
|
||||
RCTSurfaceView *_Nullable _view;
|
||||
RCTTouchHandler *_Nullable _touchHandler;
|
||||
RCTSurfaceTouchHandler *_Nullable _touchHandler;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
|
@ -71,6 +71,8 @@
|
|||
|
||||
_stage = RCTSurfaceStageSurfaceDidInitialize;
|
||||
|
||||
_touchHandler = [RCTSurfaceTouchHandler new];
|
||||
|
||||
[self _run];
|
||||
}
|
||||
|
||||
|
@ -102,6 +104,7 @@
|
|||
|
||||
if (!_view) {
|
||||
_view = [[RCTSurfaceView alloc] initWithSurface:(RCTSurface *)self];
|
||||
[_touchHandler attachToView:_view];
|
||||
}
|
||||
|
||||
return _view;
|
||||
|
|
Loading…
Reference in New Issue