| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #import "WXComponent+Events.h" |
| #import "WXComponent.h" |
| #import "WXComponent_internal.h" |
| #import "WXSDKInstance.h" |
| #import "WXComponentManager.h" |
| #import "WXAssert.h" |
| #import "WXUtility.h" |
| #import "WXSDKManager.h" |
| #import "WXSDKInstance_private.h" |
| #import "WXDefine.h" |
| #import "WXRecycleListComponent.h" |
| #import "WXRecycleListDataManager.h" |
| #import <objc/runtime.h> |
| #import <UIKit/UIGestureRecognizerSubclass.h> |
| #import "WXComponent+PseudoClassManagement.h" |
| #import "WXCoreBridge.h" |
| |
| #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" |
| |
| @interface UITouch (WXTouchGestureRecognizer) |
| @property (nonatomic, strong) NSNumber *wx_identifier; |
| @property (nonatomic, strong) NSNumber *wx_stopPropagation; |
| @end |
| |
| @implementation UITouch (WXTouchGestureRecognizer) |
| - (NSNumber *)wx_identifier |
| { |
| return objc_getAssociatedObject(self, _cmd); |
| } |
| |
| - (void)setWx_identifier:(NSNumber *)wx_identifier |
| { |
| objc_setAssociatedObject(self, @selector(wx_identifier), wx_identifier, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| } |
| |
| - (NSNumber *)wx_stopPropagation |
| { |
| return objc_getAssociatedObject(self, _cmd); |
| } |
| |
| - (void)setWx_stopPropagation:(NSNumber *)wx_stopPropagation |
| { |
| objc_setAssociatedObject(self, @selector(wx_stopPropagation), wx_stopPropagation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| } |
| @end |
| |
| @interface UIGestureRecognizer (WXGesture) |
| @property (nonatomic, strong) NSNumber *wx_identifier; |
| @end |
| |
| @implementation UIGestureRecognizer (WXGesture) |
| - (NSNumber *)wx_identifier |
| { |
| NSNumber *identifier = objc_getAssociatedObject(self, _cmd); |
| if (!identifier) { |
| static NSUInteger _gestureIdentifier; |
| identifier = @(_gestureIdentifier++); |
| self.wx_identifier = identifier; |
| } |
| |
| return identifier; |
| } |
| |
| - (void)setWx_identifier:(NSNumber *)wx_identifier |
| { |
| objc_setAssociatedObject(self, @selector(wx_identifier), wx_identifier, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| } |
| @end |
| |
| @interface WXTouchGestureRecognizer : UIGestureRecognizer |
| |
| @property (nonatomic, assign) BOOL listenTouchStart; |
| @property (nonatomic, assign) BOOL listenTouchMove; |
| @property (nonatomic, assign) BOOL listenTouchEnd; |
| @property (nonatomic, assign) BOOL listenTouchCancel; |
| @property (nonatomic, assign) BOOL listenPseudoTouch; |
| |
| - (instancetype)initWithComponent:(WXComponent *)component NS_DESIGNATED_INITIALIZER; |
| |
| @end |
| |
| @interface WXEventManager :NSObject |
| + (instancetype) sharedManager; |
| - (BOOL)stopPropagation:(NSString *)instanceId ref:(NSString *)ref type:(NSString *)type params:(NSDictionary *)params; |
| @end |
| |
| @implementation WXEventManager |
| |
| - (instancetype) init |
| { |
| self = [super init]; |
| return self; |
| } |
| |
| + (instancetype)sharedManager |
| { |
| static id _sharedInstance = nil; |
| static dispatch_once_t oncePredicate; |
| dispatch_once(&oncePredicate, ^{ |
| _sharedInstance = [[self alloc] init]; |
| }); |
| return _sharedInstance; |
| } |
| |
| - (BOOL)stopPropagation:(NSString *)instanceId ref:(NSString *)ref type:(NSString *)type params:(NSDictionary *)params |
| { |
| JSValue *value = [[WXSDKManager bridgeMgr] fireEventWithResult:instanceId ref:ref type:type params:params domChanges:nil]; |
| |
| if ([value.toString isEqualToString:@"true"]) { |
| return YES; |
| } |
| return NO; |
| } |
| |
| @end |
| |
| @implementation WXComponent (Events) |
| |
| #pragma mark Public |
| |
| - (void)fireEvent:(NSString *)eventName params:(NSDictionary *)params |
| { |
| [self fireEvent:eventName params:params domChanges:nil]; |
| } |
| |
| - (void)fireEvent:(NSString *)eventName params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges |
| { |
| NSMutableDictionary *dict = [NSMutableDictionary dictionary]; |
| NSTimeInterval timeSp = [[NSDate date] timeIntervalSince1970] * 1000; |
| [dict setObject:@(timeSp) forKey:@"timestamp"]; |
| if (params) { |
| [dict addEntriesFromDictionary:params]; |
| } |
| WXRecycleListComponent *recyleListComponent = (WXRecycleListComponent*)[self getRecycleListComponent]; |
| if (recyleListComponent) { |
| NSIndexPath *indexPath = [((UICollectionView*)recyleListComponent.view) indexPathForItemAtPoint:[self.view.superview |
| convertPoint:self.view.center toView:recyleListComponent.view]]; |
| NSString *templateId = [self recursiveFindTemplateIdWithComponent:self]; |
| NSString *virtualComponentId = [recyleListComponent.dataManager virtualComponentIdWithIndexPath:indexPath templateId:templateId]; |
| if (virtualComponentId) { |
| dict[@"componentId"] = virtualComponentId; |
| } |
| } |
| |
| NSArray *handlerArguments = [self _paramsForEvent:eventName]; |
| NSString *ref = _templateComponent ? _templateComponent.ref : self.ref; |
| [[WXSDKManager bridgeMgr] fireEvent:self.weexInstance.instanceId ref:ref type:eventName params:dict domChanges:domChanges handlerArguments:handlerArguments]; |
| } |
| |
| - (NSString *)recursiveFindTemplateIdWithComponent:(WXComponent *)component |
| { |
| if (!component) { |
| return nil; |
| } |
| if ([component isKindOfClass:NSClassFromString(@"WXCellSlotComponent")]) { |
| return nil; |
| } |
| if (component.attributes[@"@templateId"]) { |
| return component.attributes[@"@templateId"]; |
| } |
| return [self recursiveFindTemplateIdWithComponent:component.supercomponent]; |
| } |
| |
| - (void)addEvent:(NSString *)addEventName |
| { |
| WXAssertMainThread(); |
| } |
| |
| - (void)removeEvent:(NSString *)removeEventName |
| { |
| WXAssertMainThread(); |
| } |
| |
| #pragma mark Add & Remove Event |
| |
| #define WX_ADD_EVENT(eventName, addSelector) \ |
| if ([addEventName isEqualToString:@#eventName]) {\ |
| [self addSelector];\ |
| } |
| |
| #define WX_ADD_EVENTS(eventName1,eventName2, addSelector) \ |
| if ([addEventName isEqualToString:@#eventName1]||[addEventName isEqualToString:@#eventName2]) {\ |
| [self addSelector:addEventName];\ |
| } |
| |
| #define WX_REMOVE_EVENT(eventName, removeSelector) \ |
| if ([removeEventName isEqualToString:@#eventName]) {\ |
| [self removeSelector];\ |
| } |
| |
| #define WX_REMOVE_EVENTS(eventName1,eventName2, removeSelector) \ |
| if ([removeEventName isEqualToString:@#eventName1]||[removeEventName isEqualToString:@#eventName2]) {\ |
| [self removeSelector];\ |
| } |
| |
| - (void)_initEvents:(NSArray *)events |
| { |
| for (NSString *addEventName in events) { |
| [self _addEventOnMainThread:addEventName]; |
| } |
| } |
| |
| - (void)_initPseudoEvents:(BOOL)isListenPseudoTouch |
| { |
| if(isListenPseudoTouch) { |
| self.touchGesture.listenPseudoTouch = YES; |
| } |
| } |
| |
| - (void)_addEventOnMainThread:(NSString *)addEventName |
| { |
| if (![self isViewLoaded]) { |
| //This action will be ignored While the view is loaded, |
| //then it will initEvent according to the records in _events |
| return; |
| } |
| WX_ADD_EVENT(appear, addAppearEvent) |
| WX_ADD_EVENT(disappear, addDisappearEvent) |
| |
| WX_ADD_EVENT(click, addClickEvent) |
| WX_ADD_EVENT(swipe, addSwipeEvent) |
| WX_ADD_EVENT(longpress, addLongPressEvent) |
| |
| WX_ADD_EVENT(panstart, addPanStartEvent) |
| WX_ADD_EVENT(panmove, addPanMoveEvent) |
| WX_ADD_EVENT(panend, addPanEndEvent) |
| |
| WX_ADD_EVENT(horizontalpan, addHorizontalPanEvent) |
| WX_ADD_EVENT(verticalpan, addVerticalPanEvent) |
| |
| WX_ADD_EVENT(touchstart, addTouchStartEvent) |
| WX_ADD_EVENT(touchmove, addTouchMoveEvent) |
| WX_ADD_EVENT(touchend, addTouchEndEvent) |
| WX_ADD_EVENT(touchcancel, addTouchCancelEvent) |
| WX_ADD_EVENT(accessibilityMagicTap, addAccessibilityMagicTapEvent) |
| |
| WX_ADD_EVENTS(stopPropagation, stoppropagation, addStopPropagationEvent) |
| |
| if(_isListenPseudoTouch) { |
| self.touchGesture.listenPseudoTouch = YES; |
| } |
| |
| [self addEvent:addEventName]; |
| } |
| |
| - (void)_removeEventOnMainThread:(NSString *)removeEventName |
| { |
| WX_REMOVE_EVENT(appear, removeAppearEvent) |
| WX_REMOVE_EVENT(disappear, removeDisappearEvent) |
| |
| WX_REMOVE_EVENT(click, removeClickEvent) |
| WX_REMOVE_EVENT(swipe, removeSwipeEvent) |
| WX_REMOVE_EVENT(longpress, removeLongPressEvent) |
| |
| WX_REMOVE_EVENT(panstart, removePanStartEvent) |
| WX_REMOVE_EVENT(panmove, removePanMoveEvent) |
| WX_REMOVE_EVENT(panend, removePanEndEvent) |
| |
| WX_REMOVE_EVENT(horizontalpan, removeHorizontalPanEvent) |
| WX_REMOVE_EVENT(verticalpan, removeVerticalPanEvent) |
| |
| WX_REMOVE_EVENT(touchstart, removeTouchStartEvent) |
| WX_REMOVE_EVENT(touchmove, removeTouchMoveEvent) |
| WX_REMOVE_EVENT(touchend, removeTouchEndEvent) |
| WX_REMOVE_EVENT(touchcancel, removeTouchCancelEvent) |
| WX_REMOVE_EVENT(accessibilityMagicTap, removeAccessibilityMagicTapEvent) |
| |
| WX_REMOVE_EVENTS(stoppropagation,stopPropagation, removeStopPropagationEvent) |
| |
| if(_isListenPseudoTouch) { |
| self.touchGesture.listenPseudoTouch = NO; |
| } |
| |
| [self removeEvent:removeEventName]; |
| } |
| |
| - (void)_removeAllEvents |
| { |
| [self removeClickEvent]; |
| [self removeLongPressEvent]; |
| [self removePanStartEvent]; |
| [self removePanMoveEvent]; |
| [self removePanEndEvent]; |
| [self removeHorizontalPanEvent]; |
| [self removeVerticalPanEvent]; |
| |
| [self removeTouchStartEvent]; |
| [self removeTouchMoveEvent]; |
| [self removeTouchEndEvent]; |
| [self removeTouchCancelEvent]; |
| [self removeSwipeEvent]; |
| [self removePseudoTouchEvent]; |
| } |
| |
| - (void)_collectSubcomponents:(NSMutableArray *)components |
| { |
| for (WXComponent* c in _subcomponents) { |
| [components addObject:c]; |
| [c _collectSubcomponents:components]; |
| } |
| } |
| |
| #pragma mark - Appear & Disappear |
| |
| - (void)addAppearEvent |
| { |
| _appearEvent = YES; |
| [self.ancestorScroller addScrollToListener:self]; |
| } |
| |
| - (void)addDisappearEvent |
| { |
| _disappearEvent = YES; |
| [self.ancestorScroller addScrollToListener:self]; |
| } |
| |
| - (void)removeAppearEvent |
| { |
| _appearEvent = NO; |
| [self.ancestorScroller removeScrollToListener:self]; |
| } |
| |
| - (void)removeDisappearEvent |
| { |
| _disappearEvent = NO; |
| [self.ancestorScroller removeScrollToListener:self]; |
| } |
| |
| - (void)removePseudoTouchEvent |
| { |
| _touchGesture.listenPseudoTouch = NO; |
| [self checkRemoveTouchGesture]; |
| } |
| |
| #pragma mark - Accessibility Event |
| |
| - (void)addAccessibilityMagicTapEvent |
| { |
| _accessibilityMagicTapEvent = YES; |
| } |
| |
| - (void)removeAccessibilityMagicTapEvent |
| { |
| _accessibilityMagicTapEvent = NO; |
| } |
| |
| #pragma mark - StopPropagation |
| |
| - (void)addStopPropagationEvent:(NSString *)stopPropagationName |
| { |
| _listenStopPropagation = YES; |
| _stopPropagationName = stopPropagationName; |
| self.touchGesture.listenTouchMove = YES; |
| } |
| |
| - (void)removeStopPropagationEvent |
| { |
| _listenStopPropagation = NO; |
| self.touchGesture.listenTouchMove = NO; |
| } |
| |
| #pragma mark - Click Event |
| |
| - (void)addClickEvent |
| { |
| if (!_tapGesture) { |
| _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClick:)]; |
| _tapGesture.delegate = self; |
| _tapGesture.cancelsTouchesInView = _cancelsTouchesInView; |
| [self.view addGestureRecognizer:_tapGesture]; |
| } |
| } |
| |
| - (void)removeClickEvent |
| { |
| if (_tapGesture) { |
| _tapGesture.delegate = nil; |
| if ([self isViewLoaded]) { |
| if ([self.view.gestureRecognizers containsObject:_tapGesture]) { |
| [self.view removeGestureRecognizer:_tapGesture]; |
| } |
| } |
| @try { |
| [_tapGesture removeTarget:self action:@selector(onClick:)]; |
| }@catch(NSException *exception) { |
| WXLog(@"%@", exception); |
| } |
| _tapGesture = nil; |
| } |
| } |
| |
| - (void)onClick:(__unused UITapGestureRecognizer *)recognizer |
| { |
| NSMutableDictionary *position = [[NSMutableDictionary alloc] initWithCapacity:4]; |
| CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; |
| if (![self isViewLoaded]) { |
| return; |
| } |
| if (!CGRectEqualToRect(self.view.frame, CGRectZero)) { |
| CGPoint pageLocation = [recognizer locationInView:self.weexInstance.rootView]; |
| CGRect frame = [self.view.superview convertRect:self.view.frame toView:self.view.window]; |
| position[@"x"] = @(frame.origin.x/scaleFactor); |
| position[@"y"] = @(frame.origin.y/scaleFactor); |
| position[@"width"] = @(frame.size.width/scaleFactor); |
| position[@"height"] = @(frame.size.height/scaleFactor); |
| position[@"pageX"] = @(pageLocation.x/scaleFactor); |
| position[@"pageY"] = @(pageLocation.y/scaleFactor); |
| } |
| [self fireEvent:@"click" params:@{@"position":position}]; |
| } |
| |
| #pragma mark - Swipe event |
| |
| - (void)addSwipeEvent |
| { |
| if (_swipeGestures) { |
| return; |
| } |
| |
| _swipeGestures = [NSMutableArray arrayWithCapacity:4]; |
| |
| // It's a little weird because the UISwipeGestureRecognizer.direction property is an options-style bit mask, but each recognizer can only handle one direction |
| SEL selector = @selector(onSwipe:); |
| UISwipeGestureRecognizer *upSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self |
| action:selector]; |
| upSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionUp; |
| upSwipeRecognizer.delegate = self; |
| [_swipeGestures addObject:upSwipeRecognizer]; |
| [self.view addGestureRecognizer:upSwipeRecognizer]; |
| |
| UISwipeGestureRecognizer *downSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self |
| action:selector]; |
| downSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionDown; |
| downSwipeRecognizer.delegate = self; |
| [_swipeGestures addObject:downSwipeRecognizer]; |
| [self.view addGestureRecognizer:downSwipeRecognizer]; |
| |
| UISwipeGestureRecognizer *rightSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self |
| action:selector]; |
| rightSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionRight; |
| rightSwipeRecognizer.delegate = self; |
| [_swipeGestures addObject:rightSwipeRecognizer]; |
| [self.view addGestureRecognizer:rightSwipeRecognizer]; |
| |
| UISwipeGestureRecognizer *leftSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self |
| action:selector]; |
| leftSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionLeft; |
| leftSwipeRecognizer.delegate = self; |
| [_swipeGestures addObject:leftSwipeRecognizer]; |
| [self.view addGestureRecognizer:leftSwipeRecognizer]; |
| } |
| |
| - (void)removeSwipeEvent |
| { |
| if (_swipeGestures == nil) { |
| return; |
| } |
| |
| @try { |
| for (UISwipeGestureRecognizer *recognizer in _swipeGestures) { |
| recognizer.delegate = nil; |
| if([self isViewLoaded]) { |
| if ([[self.view gestureRecognizers] containsObject:recognizer]) { |
| [self.view removeGestureRecognizer:recognizer]; |
| } |
| } |
| [recognizer removeTarget:self action:@selector(onSwipe:)]; |
| } |
| }@catch(NSException *exception) { |
| WXLog(@"%@", exception); |
| } |
| _swipeGestures = nil; |
| } |
| |
| - (void)onSwipe:(UISwipeGestureRecognizer *)gesture |
| { |
| if (![self isViewLoaded]) { |
| return; |
| } |
| |
| UISwipeGestureRecognizerDirection direction = gesture.direction; |
| |
| NSString *directionString; |
| switch(direction) { |
| case UISwipeGestureRecognizerDirectionLeft: |
| directionString = @"left"; |
| break; |
| case UISwipeGestureRecognizerDirectionRight: |
| directionString = @"right"; |
| break; |
| case UISwipeGestureRecognizerDirectionUp: |
| directionString = @"up"; |
| break; |
| case UISwipeGestureRecognizerDirectionDown: |
| directionString = @"down"; |
| break; |
| default: |
| directionString = @"unknown"; |
| break; |
| } |
| |
| CGPoint screenLocation = [gesture locationInView:self.view.window]; |
| CGPoint pageLoacation = [gesture locationInView:self.weexInstance.rootView]; |
| NSDictionary *resultTouch = [self touchResultWithScreenLocation:screenLocation pageLocation:pageLoacation identifier:gesture.wx_identifier]; |
| [self fireEvent:@"swipe" params:@{@"direction":directionString, @"changedTouches":resultTouch ? @[resultTouch] : @[]}]; |
| } |
| |
| #pragma mark - Long Press |
| |
| - (void)addLongPressEvent |
| { |
| if (!_longPressGesture) { |
| _longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPress:)]; |
| _longPressGesture.delegate = self; |
| [self.view addGestureRecognizer:_longPressGesture]; |
| } |
| } |
| |
| - (void)removeLongPressEvent |
| { |
| if (_longPressGesture) { |
| _longPressGesture.delegate = nil; |
| if ([self isViewLoaded]) { |
| if([[self.view gestureRecognizers] containsObject:_longPressGesture]) { |
| [self.view removeGestureRecognizer:_longPressGesture]; |
| } |
| } |
| @try { |
| [_longPressGesture removeTarget:self action:@selector(onLongPress:)]; |
| }@catch(NSException * exception) { |
| WXLog(@"%@", exception); |
| } |
| _longPressGesture = nil; |
| } |
| } |
| |
| - (void)onLongPress:(UILongPressGestureRecognizer *)gesture |
| { |
| if (![self isViewLoaded]) { |
| return; |
| } |
| |
| if (gesture.state == UIGestureRecognizerStateBegan) { |
| CGPoint screenLocation = [gesture locationInView:self.view.window]; |
| CGPoint pageLoacation = [gesture locationInView:self.weexInstance.rootView]; |
| NSDictionary *resultTouch = [self touchResultWithScreenLocation:screenLocation pageLocation:pageLoacation identifier:gesture.wx_identifier]; |
| [self fireEvent:@"longpress" params:@{@"changedTouches":resultTouch ? @[resultTouch] : @[]}]; |
| } else if (gesture.state == UIGestureRecognizerStateEnded) { |
| gesture.wx_identifier = nil; |
| } |
| } |
| |
| #pragma mark - Pan |
| |
| - (void)addPanGesture |
| { |
| if (!_panGesture) { |
| _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)]; |
| _panGesture.delegate = self; |
| [self.view addGestureRecognizer:_panGesture]; |
| } |
| } |
| |
| - (void)addPanStartEvent |
| { |
| _listenPanStart = YES; |
| [self addPanGesture]; |
| } |
| |
| - (void)addPanMoveEvent |
| { |
| _listenPanMove = YES; |
| [self addPanGesture]; |
| } |
| |
| - (void)addPanEndEvent |
| { |
| _listenPanEnd = YES; |
| [self addPanGesture]; |
| } |
| |
| - (void)addHorizontalPanEvent |
| { |
| _listenHorizontalPan = YES; |
| [self addPanGesture]; |
| } |
| |
| - (void)addVerticalPanEvent |
| { |
| _listenVerticalPan = YES; |
| [self addPanGesture]; |
| } |
| |
| |
| - (void)onPan:(UIPanGestureRecognizer *)gesture |
| { |
| if (![self isViewLoaded]) { |
| return; |
| } |
| |
| CGPoint screenLocation = [gesture locationInView:self.view.window]; |
| CGPoint pageLoacation = [gesture locationInView:self.weexInstance.rootView]; |
| NSString *eventName; |
| NSString *state = @""; |
| NSDictionary *resultTouch = [self touchResultWithScreenLocation:screenLocation pageLocation:pageLoacation identifier:gesture.wx_identifier]; |
| |
| if (gesture.state == UIGestureRecognizerStateBegan) { |
| if (_listenPanStart) { |
| eventName = @"panstart"; |
| } |
| state = @"start"; |
| } else if (gesture.state == UIGestureRecognizerStateEnded) { |
| if (_listenPanEnd) { |
| eventName = @"panend"; |
| } |
| state = @"end"; |
| gesture.wx_identifier = nil; |
| } else if (gesture.state == UIGestureRecognizerStateChanged) { |
| if (_listenPanMove) { |
| eventName = @"panmove"; |
| } |
| state = @"move"; |
| } else if (gesture.state == UIGestureRecognizerStateCancelled) { |
| state = @"cancel"; |
| } |
| |
| CGPoint translation = [_panGesture translationInView:self.view]; |
| |
| if (_listenHorizontalPan && (gesture.state != UIGestureRecognizerStateBegan || fabs(translation.y) <= fabs(translation.x))) { |
| [self fireEvent:@"horizontalpan" params:@{@"state":state, @"changedTouches":resultTouch ? @[resultTouch] : @[]}]; |
| } |
| |
| if (_listenVerticalPan && (gesture.state != UIGestureRecognizerStateBegan || fabs(translation.y) >= fabs(translation.x))) { |
| [self fireEvent:@"verticalpan" params:@{@"state":state, @"changedTouches":resultTouch ? @[resultTouch] : @[]}]; |
| } |
| |
| if (eventName) { |
| [self fireEvent:eventName params:@{@"changedTouches":resultTouch ? @[resultTouch] : @[]}]; |
| } |
| } |
| |
| - (void)removePanStartEvent |
| { |
| _listenPanStart = NO; |
| [self checkRemovePanGesture]; |
| } |
| |
| - (void)removePanMoveEvent |
| { |
| _listenPanMove = NO; |
| [self checkRemovePanGesture]; |
| } |
| |
| - (void)removePanEndEvent |
| { |
| _listenPanEnd = NO; |
| [self checkRemovePanGesture]; |
| } |
| |
| - (void)removeHorizontalPanEvent |
| { |
| _listenHorizontalPan = NO; |
| [self checkRemovePanGesture]; |
| } |
| |
| - (void)removeVerticalPanEvent |
| { |
| _listenVerticalPan = NO; |
| [self checkRemovePanGesture]; |
| } |
| |
| - (void)checkRemovePanGesture |
| { |
| if (_panGesture |
| && !_listenPanStart && !_listenPanMove && !_listenPanEnd |
| && !_listenHorizontalPan && !_listenVerticalPan |
| ) { |
| |
| if ([self isViewLoaded]) { |
| if ([[self.view gestureRecognizers] containsObject:_panGesture]) { |
| [self.view removeGestureRecognizer:_panGesture]; |
| } |
| } |
| |
| _panGesture.delegate = nil; |
| @try { |
| [_panGesture removeTarget:self action:@selector(onPan:)]; |
| }@catch(NSException * exception) { |
| WXLog(@"%@", exception); |
| } |
| _panGesture = nil; |
| } |
| } |
| |
| #pragma mark - Touch Event |
| |
| - (WXTouchGestureRecognizer *)touchGesture |
| { |
| if (!_touchGesture) { |
| _touchGesture = [[WXTouchGestureRecognizer alloc] initWithComponent:self]; |
| _touchGesture.delegate = self; |
| [self.view addGestureRecognizer:_touchGesture]; |
| } |
| return _touchGesture; |
| } |
| |
| - (void)addTouchStartEvent |
| { |
| self.touchGesture.listenTouchStart = YES; |
| } |
| |
| - (void)addTouchMoveEvent |
| { |
| self.touchGesture.listenTouchMove = YES; |
| } |
| |
| - (void)addTouchEndEvent |
| { |
| self.touchGesture.listenTouchEnd = YES; |
| } |
| |
| - (void)addTouchCancelEvent |
| { |
| self.touchGesture.listenTouchCancel = YES; |
| } |
| |
| - (void)removeTouchStartEvent |
| { |
| _touchGesture.listenTouchStart = NO; |
| [self checkRemoveTouchGesture]; |
| } |
| |
| - (void)removeTouchMoveEvent |
| { |
| _touchGesture.listenTouchMove = NO; |
| [self checkRemoveTouchGesture]; |
| } |
| |
| - (void)removeTouchEndEvent |
| { |
| _touchGesture.listenTouchEnd = NO; |
| [self checkRemoveTouchGesture]; |
| } |
| |
| - (void)removeTouchCancelEvent |
| { |
| _touchGesture.listenTouchCancel = NO; |
| [self checkRemoveTouchGesture]; |
| } |
| |
| - (void)checkRemoveTouchGesture |
| { |
| if (_touchGesture && !_touchGesture.listenTouchStart && !_touchGesture.listenTouchMove && !_touchGesture.listenTouchEnd && !_touchGesture.listenTouchCancel && !_touchGesture.listenPseudoTouch) { |
| _touchGesture.delegate = nil; |
| if ([self isViewLoaded]) { |
| if ([[self.view gestureRecognizers] containsObject:_touchGesture]) { |
| [self.view removeGestureRecognizer:_touchGesture]; |
| } |
| } |
| _touchGesture = nil; |
| } |
| } |
| |
| - (BOOL)gestureShouldStopPropagation:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch |
| { |
| if(touch.wx_stopPropagation && [touch.wx_stopPropagation isEqualToNumber:@1]){ |
| return NO; |
| } |
| else |
| { |
| if (_listenStopPropagation) |
| { |
| NSString *ref = _templateComponent ? _templateComponent.ref : self.ref; |
| CGPoint screenLocation = [touch locationInView:touch.window]; |
| CGPoint pageLocation = [touch locationInView:self.weexInstance.rootView]; |
| NSDictionary *resultTouch = [self touchResultWithScreenLocation:screenLocation pageLocation:pageLocation identifier:touch.wx_identifier]; |
| NSString *touchState; |
| if (touch.phase == UITouchPhaseBegan) { |
| touchState = @"start"; |
| } |
| else if (touch.phase == UITouchPhaseMoved){ |
| touchState = @"move"; |
| } |
| else{ |
| touchState = @"end"; |
| } |
| BOOL stopPropagation = [[WXEventManager sharedManager]stopPropagation:self.weexInstance.instanceId ref:ref type:_stopPropagationName params:@{@"changedTouches":resultTouch ? @[resultTouch] : @[],@"action":touchState}]; |
| touch.wx_stopPropagation = stopPropagation ? @1 : @0; |
| |
| //only custom event on custom component will make not receive touch |
| //you can use custom-event="yes" to enable this feature |
| return _customEvent ? !stopPropagation : YES; |
| } |
| } |
| return YES; |
| } |
| |
| #pragma mark - UIGestureRecognizerDelegate |
| - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch |
| { |
| return [self gestureShouldStopPropagation:gestureRecognizer shouldReceiveTouch:touch]; |
| } |
| |
| - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer |
| { |
| if (gestureRecognizer == _panGesture) { |
| CGPoint translation = [_panGesture translationInView:self.view]; |
| if (_listenHorizontalPan && !_listenVerticalPan && fabs(translation.y) > fabs(translation.x)) { |
| return NO; |
| } |
| } |
| return YES; |
| } |
| |
| - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer |
| { |
| NSString * panGestureRecog = [NSString stringWithFormat:@"%@%@%@%@%@%@",@"UIScrollV", @"iewPanG", @"estur",@"eRecog",@"nize",@"r"]; |
| NSString * textTap = [NSString stringWithFormat:@"%@%@%@%@%@",@"UITe",@"xtTa",@"pReco",@"gniz",@"er"]; |
| // trigger touches |
| if ([gestureRecognizer isKindOfClass:[WXTouchGestureRecognizer class]]) { |
| return YES; |
| } |
| // swipe and scroll |
| if ([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:NSClassFromString(panGestureRecog)]) { |
| return YES; |
| } |
| // onclick and textviewInput |
| if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass: NSClassFromString(textTap)]) { |
| return YES; |
| } |
| |
| return NO; |
| } |
| |
| - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer |
| { |
| if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && |
| [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { |
| if (otherGestureRecognizer.state != UIGestureRecognizerStateFailed) { |
| if ([gestureRecognizer view].wx_component != nil && [otherGestureRecognizer view].wx_component != nil) { |
| return YES; |
| } |
| } |
| } |
| |
| return NO; |
| } |
| |
| #pragma mark - Utils |
| |
| - (NSDictionary *)touchResultWithScreenLocation:(CGPoint)screenLocation pageLocation:(CGPoint)pageLocation identifier:(NSNumber *)identifier |
| { |
| NSMutableDictionary *resultTouch = [[NSMutableDictionary alloc] initWithCapacity:5]; |
| CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; |
| resultTouch[@"screenX"] = @(screenLocation.x/scaleFactor); |
| resultTouch[@"screenY"] = @(screenLocation.y/scaleFactor); |
| resultTouch[@"pageX"] = @(pageLocation.x/scaleFactor); |
| resultTouch[@"pageY"] = @(pageLocation.y/scaleFactor); |
| resultTouch[@"identifier"] = identifier; |
| |
| return resultTouch; |
| } |
| |
| // find virtual component's root component |
| - (WXComponent*)getRecycleListComponent |
| { |
| if ([self isKindOfClass:[WXRecycleListComponent class]]) { |
| return self; |
| } |
| if ([self.ref isEqualToString:WX_SDK_ROOT_REF]) { |
| return nil; |
| } |
| return [self.supercomponent getRecycleListComponent]; |
| } |
| |
| @end |
| |
| @implementation WXTouchGestureRecognizer |
| { |
| __weak WXComponent *_component; |
| NSUInteger _touchIdentifier; |
| } |
| |
| - (instancetype)initWithTarget:(id)target action:(SEL)action |
| { |
| return [self initWithComponent:nil]; |
| } |
| |
| - (instancetype)initWithComponent:(WXComponent *)component |
| { |
| if (self = [super initWithTarget:self action:@selector(touchResponse:)]) { |
| _component = component; |
| |
| _listenTouchStart = NO; |
| _listenTouchEnd = NO; |
| _listenTouchMove = NO; |
| _listenTouchCancel = NO; |
| |
| self.cancelsTouchesInView = NO; |
| } |
| |
| return self; |
| } |
| |
| - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event |
| { |
| [super touchesBegan:touches withEvent:event]; |
| |
| if (_listenTouchStart) { |
| [self fireTouchEvent:@"touchstart" withTouches:touches]; |
| } |
| if(_listenPseudoTouch) { |
| NSMutableDictionary *styles = [_component getPseudoClassStyles:@"active"]; |
| [_component updatePseudoClassStyles:styles]; |
| } |
| |
| } |
| |
| - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event |
| { |
| [super touchesMoved:touches withEvent:event]; |
| |
| if (_listenTouchMove) { |
| [self fireTouchEvent:@"touchmove" withTouches:touches]; |
| } |
| } |
| |
| - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event |
| { |
| |
| [super touchesEnded:touches withEvent:event]; |
| |
| if (_listenTouchEnd) { |
| [self fireTouchEvent:@"touchend" withTouches:touches]; |
| } |
| if(_listenPseudoTouch) { |
| [self recoveryPseudoStyles:_component.styles]; |
| } |
| |
| } |
| |
| - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event |
| { |
| [super touchesCancelled:touches withEvent:event]; |
| |
| if (_listenTouchCancel) { |
| [self fireTouchEvent:@"touchcancel" withTouches:touches]; |
| } |
| if(_listenPseudoTouch) { |
| [self recoveryPseudoStyles:_component.styles]; |
| } |
| } |
| |
| - (void)fireTouchEvent:(NSString *)eventName withTouches:(NSSet<UITouch *> *)touches |
| { |
| if (_component == nil) { |
| return; |
| } |
| |
| NSMutableArray *resultTouches = [NSMutableArray new]; |
| |
| CGPoint accmOffset = CGPointZero; |
| UIView* rootView = _component.weexInstance.rootView; |
| // UIView* view = self.view; |
| // while (view && view != rootView) { |
| // if ([view isKindOfClass:[UIScrollView class]]) { |
| // CGPoint offset = ((UIScrollView*)view).contentOffset; |
| // accmOffset.x += offset.x; |
| // accmOffset.y += offset.y; |
| // } |
| // view = view.superview; |
| // } |
| |
| for (UITouch *touch in touches) { |
| CGPoint screenLocation = [touch locationInView:touch.window]; |
| CGPoint pageLocation = [touch locationInView:rootView]; |
| pageLocation.x += accmOffset.x; |
| pageLocation.y += accmOffset.y; |
| |
| if (!touch.wx_identifier) { |
| touch.wx_identifier = @(_touchIdentifier++); |
| } |
| NSDictionary *resultTouch = [_component touchResultWithScreenLocation:screenLocation pageLocation:pageLocation identifier:touch.wx_identifier]; |
| NSMutableDictionary * mutableResultTouch = [resultTouch mutableCopy]; |
| |
| float value = touch.force*60; |
| float maxValue = touch.maximumPossibleForce*60; |
| if (touch.maximumPossibleForce) { |
| // the forece value will be range 1 from 0. |
| [mutableResultTouch setObject:[NSNumber numberWithFloat:value/maxValue] forKey:@"force"]; |
| }else { |
| [mutableResultTouch setObject:[NSNumber numberWithFloat:0.0] forKey:@"force"]; |
| } |
| |
| if (mutableResultTouch) { // component is nil, mutableResultTouch will be nil |
| [resultTouches addObject:mutableResultTouch]; |
| } |
| } |
| |
| [_component fireEvent:eventName params:@{@"changedTouches":resultTouches ?: @[]}]; |
| } |
| |
| - (void)recoveryPseudoStyles:(NSDictionary *)styles |
| { |
| [_component recoveryPseudoStyles:styles]; |
| } |
| |
| - (void)touchResponse:(UIGestureRecognizer *)gesture |
| { |
| |
| } |
| |
| @end |