123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * 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 <React/RCTUtils.h>
- #import <React/RCTViewComponentView.h>
- #import <UIKit/UIGestureRecognizerSubclass.h>
- #import "RCTConversions.h"
- #import "RCTTouchableComponentViewProtocol.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;
- };
- typedef NS_ENUM(NSInteger, RCTTouchEventType) {
- RCTTouchEventTypeTouchStart,
- RCTTouchEventTypeTouchMove,
- RCTTouchEventTypeTouchEnd,
- RCTTouchEventTypeTouchCancel,
- };
- struct ActiveTouch {
- Touch touch;
- SharedTouchEventEmitter eventEmitter;
- /*
- * A component view on which the touch was begun.
- */
- __strong UIView<RCTComponentViewProtocol> *componentView = nil;
- 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:activeTouch.componentView];
- 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(touchEventEmitterAtPoint:)]) {
- activeTouch.eventEmitter = [(id<RCTTouchableComponentViewProtocol>)componentView
- touchEventEmitterAtPoint:[uiTouch locationInView:componentView]];
- activeTouch.touch.target = (Tag)componentView.tag;
- }
- activeTouch.componentView = componentView;
- 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) {
- auto iterator = _activeTouches.find(touch);
- assert(iterator != _activeTouches.end() && "Inconsistency between local and UIKit touch registries");
- if (iterator == _activeTouches.end()) {
- continue;
- }
- UpdateActiveTouchWithUITouch(iterator->second, touch, _rootComponentView);
- }
- }
- - (void)_unregisterTouches:(NSSet<UITouch *> *)touches
- {
- for (UITouch *touch in touches) {
- auto iterator = _activeTouches.find(touch);
- assert(iterator != _activeTouches.end() && "Inconsistency between local and UIKit touch registries");
- if (iterator == _activeTouches.end()) {
- continue;
- }
- auto &activeTouch = iterator->second;
- _identifierPool.enqueue(activeTouch.touch.identifier);
- _activeTouches.erase(touch);
- }
- }
- - (std::vector<ActiveTouch>)_activeTouchesFromTouches:(NSSet<UITouch *> *)touches
- {
- std::vector<ActiveTouch> activeTouches;
- activeTouches.reserve(touches.count);
- for (UITouch *touch in touches) {
- auto iterator = _activeTouches.find(touch);
- assert(iterator != _activeTouches.end() && "Inconsistency between local and UIKit touch registries");
- if (iterator == _activeTouches.end()) {
- continue;
- }
- activeTouches.push_back(iterator->second);
- }
- return activeTouches;
- }
- - (void)_dispatchActiveTouches:(std::vector<ActiveTouch>)activeTouches eventType:(RCTTouchEventType)eventType
- {
- TouchEvent event = {};
- std::unordered_set<ActiveTouch, ActiveTouch::Hasher, ActiveTouch::Comparator> changedActiveTouches = {};
- std::unordered_set<SharedTouchEventEmitter> uniqueEventEmitters = {};
- BOOL isEndishEventType = eventType == RCTTouchEventTypeTouchEnd || eventType == RCTTouchEventTypeTouchCancel;
- for (const auto &activeTouch : activeTouches) {
- if (!activeTouch.eventEmitter) {
- continue;
- }
- changedActiveTouches.insert(activeTouch);
- event.changedTouches.insert(activeTouch.touch);
- uniqueEventEmitters.insert(activeTouch.eventEmitter);
- }
- for (const auto &pair : _activeTouches) {
- if (!pair.second.eventEmitter) {
- continue;
- }
- if (isEndishEventType && event.changedTouches.find(pair.second.touch) != event.changedTouches.end()) {
- continue;
- }
- event.touches.insert(pair.second.touch);
- }
- for (const auto &eventEmitter : uniqueEventEmitters) {
- event.targetTouches.clear();
- for (const auto &pair : _activeTouches) {
- if (pair.second.eventEmitter == eventEmitter) {
- event.targetTouches.insert(pair.second.touch);
- }
- }
- switch (eventType) {
- case RCTTouchEventTypeTouchStart:
- eventEmitter->onTouchStart(event);
- break;
- case RCTTouchEventTypeTouchMove:
- eventEmitter->onTouchMove(event);
- break;
- case RCTTouchEventTypeTouchEnd:
- eventEmitter->onTouchEnd(event);
- break;
- case RCTTouchEventTypeTouchCancel:
- eventEmitter->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 _dispatchActiveTouches:[self _activeTouchesFromTouches: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 _dispatchActiveTouches:[self _activeTouchesFromTouches:touches] eventType:RCTTouchEventTypeTouchMove];
- self.state = UIGestureRecognizerStateChanged;
- }
- - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- [super touchesEnded:touches withEvent:event];
- [self _updateTouches:touches];
- [self _dispatchActiveTouches:[self _activeTouchesFromTouches: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 _dispatchActiveTouches:[self _activeTouchesFromTouches:touches] eventType:RCTTouchEventTypeTouchCancel];
- [self _unregisterTouches:touches];
- if (AllTouchesAreCancelledOrEnded(event.allTouches)) {
- self.state = UIGestureRecognizerStateCancelled;
- } else if (AnyTouchesChanged(event.allTouches)) {
- self.state = UIGestureRecognizerStateChanged;
- }
- }
- - (void)reset
- {
- [super reset];
- if (_activeTouches.size() != 0) {
- std::vector<ActiveTouch> activeTouches;
- activeTouches.reserve(_activeTouches.size());
- for (auto const &pair : _activeTouches) {
- activeTouches.push_back(pair.second);
- }
- [self _dispatchActiveTouches:activeTouches eventType:RCTTouchEventTypeTouchCancel];
- // Force-unregistering all the touches.
- _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
|