1// 2// RNGestureHandlerButton.m 3// RNGestureHandler 4// 5// Created by Krzysztof Magiera on 12/10/2017. 6// Copyright © 2017 Software Mansion. All rights reserved. 7// 8 9#import "RNGestureHandlerButton.h" 10 11#import <UIKit/UIKit.h> 12 13/** 14 * Gesture Handler Button components overrides standard mechanism used by RN 15 * to determine touch target, which normally would reurn the UIView that is placed 16 * as the deepest element in the view hierarchy. 17 * It's done this way as it allows for the actual target determination to run in JS 18 * where we can travers up the view ierarchy to find first element that want to became 19 * JS responder. 20 * 21 * Since we want to use native button (or actually a `UIControl`) we need to determine 22 * the target in native. This makes it impossible for JS responder based components to 23 * function as a subviews of the button component. Here we override `hitTest:withEvent:` 24 * method and we only determine the target to be either a subclass of `UIControl` or a 25 * view that has gesture recognizers registered. 26 * 27 * This "default" behaviour of target determinator should be sufficient in most of the 28 * cases as in fact it is not that common UI pattern to have many nested buttons (usually 29 * there are just two levels e.g. when you have clickable table cells with additional 30 * buttons). In cases when the default behaviour is insufficient it is recommended to use 31 * `TapGestureHandler` instead of a button which gives much better flexibility as far as 32 * controlling the touch flow. 33 */ 34@implementation RNGestureHandlerButton 35 36- (instancetype)init 37{ 38 self = [super init]; 39 if (self) { 40 _hitTestEdgeInsets = UIEdgeInsetsZero; 41 _userEnabled = YES; 42#if !TARGET_OS_TV 43 [self setExclusiveTouch:YES]; 44#endif 45 } 46 return self; 47} 48 49- (BOOL)shouldHandleTouch:(UIView *)view 50{ 51 if ([view isKindOfClass:[RNGestureHandlerButton class]]) { 52 RNGestureHandlerButton *button = (RNGestureHandlerButton *)view; 53 return button.userEnabled; 54 } 55 56 return [view isKindOfClass:[UIControl class]] || [view.gestureRecognizers count] > 0; 57} 58 59- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 60{ 61 if (UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero)) { 62 return [super pointInside:point withEvent:event]; 63 } 64 CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets); 65 return CGRectContainsPoint(hitFrame, point); 66} 67 68- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 69{ 70 UIView *inner = [super hitTest:point withEvent:event]; 71 while (inner && ![self shouldHandleTouch:inner]) { 72 inner = inner.superview; 73 } 74 return inner; 75} 76 77@end 78