254 lines
8.6 KiB
Mathematica
254 lines
8.6 KiB
Mathematica
|
#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
|
||
|
|
||
|
|
||
|
@implementation RNGestureHandler {
|
||
|
NSArray<NSNumber *> *_handlersToWaitFor;
|
||
|
NSArray<NSNumber *> *_simultaniousHandlers;
|
||
|
UIEdgeInsets _hitSlopEdgeInsets;
|
||
|
}
|
||
|
|
||
|
- (instancetype)initWithTag:(NSNumber *)tag
|
||
|
{
|
||
|
if ((self = [super init])) {
|
||
|
_tag = tag;
|
||
|
_lastState = RNGestureHandlerStateUndetermined;
|
||
|
_hitSlopEdgeInsets = UIEdgeInsetsZero;
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (void)configure:(NSDictionary *)config
|
||
|
{
|
||
|
_handlersToWaitFor = [RCTConvert NSNumberArray:config[@"waitFor"]];
|
||
|
_simultaniousHandlers = [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 != nil) {
|
||
|
UIEdgeInsets insets = [RCTConvert UIEdgeInsets:prop];
|
||
|
_hitSlopEdgeInsets = UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (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]];
|
||
|
}
|
||
|
|
||
|
- (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
|
||
|
{
|
||
|
id touchEvent = [[RNGestureHandlerEvent alloc] initWithRactTag:reactTag
|
||
|
handlerTag:_tag
|
||
|
state:state
|
||
|
extraData:extraData];
|
||
|
|
||
|
if (state != _lastState) {
|
||
|
if (state == RNGestureHandlerStateEnd && _lastState != RNGestureHandlerStateActive) {
|
||
|
[self.emitter sendStateChangeEvent:[[RNGestureHandlerStateChange alloc] initWithRactTag:reactTag
|
||
|
handlerTag:_tag
|
||
|
state:RNGestureHandlerStateActive
|
||
|
prevState:_lastState
|
||
|
extraData:extraData]];
|
||
|
_lastState = RNGestureHandlerStateActive;
|
||
|
}
|
||
|
id stateEvent = [[RNGestureHandlerStateChange alloc] initWithRactTag:reactTag
|
||
|
handlerTag:_tag
|
||
|
state:state
|
||
|
prevState:_lastState
|
||
|
extraData:extraData];
|
||
|
[self.emitter sendStateChangeEvent:stateEvent];
|
||
|
_lastState = state;
|
||
|
}
|
||
|
|
||
|
if (state == RNGestureHandlerStateActive) {
|
||
|
[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 ([_simultaniousHandlers count]) {
|
||
|
RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
|
||
|
if (handler != nil) {
|
||
|
for (NSNumber *handlerTag in _simultaniousHandlers) {
|
||
|
if ([handler.tag isEqual:handlerTag]) {
|
||
|
return YES;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return NO;
|
||
|
}
|
||
|
|
||
|
- (void)reset
|
||
|
{
|
||
|
_lastState = RNGestureHandlerStateUndetermined;
|
||
|
}
|
||
|
|
||
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||
|
{
|
||
|
[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 (!UIEdgeInsetsEqualToEdgeInsets(_hitSlopEdgeInsets, UIEdgeInsetsZero)) {
|
||
|
CGPoint location = [touch locationInView:gestureRecognizer.view];
|
||
|
CGRect hitFrame = UIEdgeInsetsInsetRect(gestureRecognizer.view.bounds, _hitSlopEdgeInsets);
|
||
|
return CGRectContainsPoint(hitFrame, location);
|
||
|
}
|
||
|
return YES;
|
||
|
}
|
||
|
|
||
|
@end
|