GT2/Ejectable/node_modules/react-native-gesture-handler/ios/RNGestureHandler.m

345 lines
13 KiB
Objective-C

#import "RNGestureHandler.h"
#import "Handlers/RNNativeViewHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/UIView+React.h>
@interface UIGestureRecognizer (GestureHandler)
@property (nonatomic, readonly) RNGestureHandler *gestureHandler;
@end
@implementation UIGestureRecognizer (GestureHandler)
- (RNGestureHandler *)gestureHandler
{
id delegate = self.delegate;
if ([delegate isKindOfClass:[RNGestureHandler class]]) {
return (RNGestureHandler *)delegate;
}
return nil;
}
@end
typedef struct RNGHHitSlop {
CGFloat top, left, bottom, right, width, height;
} RNGHHitSlop;
static RNGHHitSlop RNGHHitSlopEmpty = { NAN, NAN, NAN, NAN, NAN, NAN };
#define RNGH_HIT_SLOP_GET(key) (prop[key] == nil ? NAN : [prop[key] doubleValue])
#define RNGH_HIT_SLOP_IS_SET(hitSlop) (!isnan(hitSlop.left) || !isnan(hitSlop.right) || \
!isnan(hitSlop.top) || !isnan(hitSlop.bottom))
#define RNGH_HIT_SLOP_INSET(key) (isnan(hitSlop.key) ? 0. : hitSlop.key)
CGRect RNGHHitSlopInsetRect(CGRect rect, RNGHHitSlop hitSlop) {
rect.origin.x -= RNGH_HIT_SLOP_INSET(left);
rect.origin.y -= RNGH_HIT_SLOP_INSET(top);
if (!isnan(hitSlop.width)) {
if (!isnan(hitSlop.right)) {
rect.origin.x = rect.size.width - hitSlop.width + RNGH_HIT_SLOP_INSET(right);
}
rect.size.width = hitSlop.width;
} else {
rect.size.width += (RNGH_HIT_SLOP_INSET(left) + RNGH_HIT_SLOP_INSET(right));
}
if (!isnan(hitSlop.height)) {
if (!isnan(hitSlop.bottom)) {
rect.origin.y = rect.size.height - hitSlop.height + RNGH_HIT_SLOP_INSET(bottom);
}
rect.size.height = hitSlop.height;
} else {
rect.size.height += (RNGH_HIT_SLOP_INSET(top) + RNGH_HIT_SLOP_INSET(bottom));
}
return rect;
}
static NSHashTable<RNGestureHandler *> *allGestureHandlers;
@implementation RNGestureHandler {
NSArray<NSNumber *> *_handlersToWaitFor;
NSArray<NSNumber *> *_simultaneousHandlers;
RNGHHitSlop _hitSlop;
uint16_t _eventCoalescingKey;
}
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super init])) {
_tag = tag;
_lastState = RNGestureHandlerStateUndetermined;
_hitSlop = RNGHHitSlopEmpty;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
allGestureHandlers = [NSHashTable weakObjectsHashTable];
});
[allGestureHandlers addObject:self];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
_handlersToWaitFor = [RCTConvert NSNumberArray:config[@"waitFor"]];
_simultaneousHandlers = [RCTConvert NSNumberArray:config[@"simultaneousHandlers"]];
id prop = config[@"enabled"];
if (prop != nil) {
self.enabled = [RCTConvert BOOL:prop];
} else {
self.enabled = YES;
}
prop = config[@"shouldCancelWhenOutside"];
if (prop != nil) {
_shouldCancelWhenOutside = [RCTConvert BOOL:prop];
} else {
_shouldCancelWhenOutside = NO;
}
prop = config[@"hitSlop"];
if ([prop isKindOfClass:[NSNumber class]]) {
_hitSlop.left = _hitSlop.right = _hitSlop.top = _hitSlop.bottom = [prop doubleValue];
} else if (prop != nil) {
_hitSlop.left = _hitSlop.right = RNGH_HIT_SLOP_GET(@"horizontal");
_hitSlop.top = _hitSlop.bottom = RNGH_HIT_SLOP_GET(@"vertical");
_hitSlop.left = RNGH_HIT_SLOP_GET(@"left");
_hitSlop.right = RNGH_HIT_SLOP_GET(@"right");
_hitSlop.top = RNGH_HIT_SLOP_GET(@"top");
_hitSlop.bottom = RNGH_HIT_SLOP_GET(@"bottom");
_hitSlop.width = RNGH_HIT_SLOP_GET(@"width");
_hitSlop.height = RNGH_HIT_SLOP_GET(@"height");
if (isnan(_hitSlop.left) && isnan(_hitSlop.right) && !isnan(_hitSlop.width)) {
RCTLogError(@"When width is set one of left or right pads need to be defined");
}
if (!isnan(_hitSlop.width) && !isnan(_hitSlop.left) && !isnan(_hitSlop.right)) {
RCTLogError(@"Cannot have all of left, right and width defined");
}
if (isnan(_hitSlop.top) && isnan(_hitSlop.bottom) && !isnan(_hitSlop.height)) {
RCTLogError(@"When height is set one of top or bottom pads need to be defined");
}
if (!isnan(_hitSlop.height) && !isnan(_hitSlop.top) && !isnan(_hitSlop.bottom)) {
RCTLogError(@"Cannot have all of top, bottom and height defined");
}
}
}
- (void)setEnabled:(BOOL)enabled
{
_enabled = enabled;
self.recognizer.enabled = enabled;
}
- (void)bindToView:(UIView *)view
{
view.userInteractionEnabled = YES;
self.recognizer.delegate = self;
[view addGestureRecognizer:self.recognizer];
}
- (void)unbindFromView
{
[self.recognizer.view removeGestureRecognizer:self.recognizer];
self.recognizer.delegate = nil;
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData
forPosition:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withNumberOfTouches:recognizer.numberOfTouches];
}
- (void)handleGesture:(UIGestureRecognizer *)recognizer
{
RNGestureHandlerEventExtraData *eventData = [self eventExtraData:recognizer];
[self sendEventsInState:self.state forViewWithTag:recognizer.view.reactTag withExtraData:eventData];
}
- (void)sendEventsInState:(RNGestureHandlerState)state
forViewWithTag:(nonnull NSNumber *)reactTag
withExtraData:(RNGestureHandlerEventExtraData *)extraData
{
if (state != _lastState) {
if (state == RNGestureHandlerStateActive) {
// Generate a unique coalescing-key each time the gesture-handler becomes active. All events will have
// the same coalescing-key allowing RCTEventDispatcher to coalesce RNGestureHandlerEvents when events are
// generated faster than they can be treated by JS thread
static uint16_t nextEventCoalescingKey = 0;
self->_eventCoalescingKey = nextEventCoalescingKey++;
} else if (state == RNGestureHandlerStateEnd && _lastState != RNGestureHandlerStateActive) {
[self.emitter sendStateChangeEvent:[[RNGestureHandlerStateChange alloc] initWithReactTag:reactTag
handlerTag:_tag
state:RNGestureHandlerStateActive
prevState:_lastState
extraData:extraData]];
_lastState = RNGestureHandlerStateActive;
}
id stateEvent = [[RNGestureHandlerStateChange alloc] initWithReactTag:reactTag
handlerTag:_tag
state:state
prevState:_lastState
extraData:extraData];
[self.emitter sendStateChangeEvent:stateEvent];
_lastState = state;
}
if (state == RNGestureHandlerStateActive) {
id touchEvent = [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag
handlerTag:_tag
state:state
extraData:extraData
coalescingKey:self->_eventCoalescingKey];
[self.emitter sendTouchEvent:touchEvent];
}
}
- (RNGestureHandlerState)state
{
switch (_recognizer.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStatePossible:
return RNGestureHandlerStateBegan;
case UIGestureRecognizerStateEnded:
return RNGestureHandlerStateEnd;
case UIGestureRecognizerStateFailed:
return RNGestureHandlerStateFailed;
case UIGestureRecognizerStateCancelled:
return RNGestureHandlerStateCancelled;
case UIGestureRecognizerStateChanged:
return RNGestureHandlerStateActive;
}
return RNGestureHandlerStateUndetermined;
}
#pragma mark UIGestureRecognizerDelegate
+ (RNGestureHandler *)findGestureHandlerByRecognizer:(UIGestureRecognizer *)recognizer
{
RNGestureHandler *handler = recognizer.gestureHandler;
if (handler != nil) {
return handler;
}
// We may try to extract "DummyGestureHandler" in case when "otherGestureRecognizer" belongs to
// a native view being wrapped with "NativeViewGestureHandler"
UIView *reactView = recognizer.view;
while (reactView != nil && reactView.reactTag == nil) {
reactView = reactView.superview;
}
for (UIGestureRecognizer *recognizer in reactView.gestureRecognizers) {
if ([recognizer isKindOfClass:[RNDummyGestureRecognizer class]]) {
return recognizer.gestureHandler;
}
}
return nil;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
if ([handler isKindOfClass:[RNNativeViewGestureHandler class]]) {
for (NSNumber *handlerTag in handler->_handlersToWaitFor) {
if ([_tag isEqual:handlerTag]) {
return YES;
}
}
}
return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([_handlersToWaitFor count]) {
RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
if (handler != nil) {
for (NSNumber *handlerTag in _handlersToWaitFor) {
if ([handler.tag isEqual:handlerTag]) {
return YES;
}
}
}
}
return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (_recognizer.state == UIGestureRecognizerStateBegan && _recognizer.state == UIGestureRecognizerStatePossible) {
return YES;
}
if ([_simultaneousHandlers count]) {
RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
if (handler != nil) {
for (NSNumber *handlerTag in _simultaneousHandlers) {
if ([handler.tag isEqual:handlerTag]) {
return YES;
}
}
}
}
return NO;
}
- (void)reset
{
_lastState = RNGestureHandlerStateUndetermined;
}
- (BOOL)containsPointInView
{
CGPoint pt = [_recognizer locationInView:_recognizer.view];
CGRect hitFrame = RNGHHitSlopInsetRect(_recognizer.view.bounds, _hitSlop);
return CGRectContainsPoint(hitFrame, pt);
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([_handlersToWaitFor count]) {
for (RNGestureHandler *handler in [allGestureHandlers allObjects]) {
if (handler != nil
&& (handler.state == RNGestureHandlerStateActive || handler->_recognizer.state == UIGestureRecognizerStateBegan)) {
for (NSNumber *handlerTag in _handlersToWaitFor) {
if ([handler.tag isEqual:handlerTag]) {
return NO;
}
}
}
}
}
[self reset];
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
// If hitSlop is set we use it to determine if a given gesture recognizer should start processing
// touch stream. This only works for negative values of hitSlop as this method won't be triggered
// unless touch startes in the bounds of the attached view. To acheve similar effect with positive
// values of hitSlop one should set hitSlop for the underlying view. This limitation is due to the
// fact that hitTest method is only available at the level of UIView
if (RNGH_HIT_SLOP_IS_SET(_hitSlop)) {
CGPoint location = [touch locationInView:gestureRecognizer.view];
CGRect hitFrame = RNGHHitSlopInsetRect(gestureRecognizer.view.bounds, _hitSlop);
return CGRectContainsPoint(hitFrame, location);
}
return YES;
}
@end