diff --git a/renderer/native/ios/renderer/HippyRootView.mm b/renderer/native/ios/renderer/HippyRootView.mm index e27f02c2b0d..ea2175dc00b 100644 --- a/renderer/native/ios/renderer/HippyRootView.mm +++ b/renderer/native/ios/renderer/HippyRootView.mm @@ -29,6 +29,7 @@ #import "HippyBridge.h" #import "HippyUIManager.h" #import "HippyDeviceBaseInfo.h" +#import "HippyTouchHandler.h" #include // Sent when the first subviews are added to the root view @@ -51,7 +52,7 @@ @interface HippyRootContentView : HippyView @property (nonatomic, readonly) BOOL contentHasAppeared; -//@property (nonatomic, strong) HippyTouchHandler *touchHandler; +@property (nonatomic, strong) HippyTouchHandler *touchHandler; @property (nonatomic, assign) int64_t startTimpStamp; - (instancetype)initWithFrame:(CGRect)frame @@ -200,7 +201,7 @@ - (BOOL)canBecomeFirstResponder { } - (void)cancelTouches { - // [[_contentView touchHandler] cancelTouch]; + [[_contentView touchHandler] cancelTouch]; } - (NSNumber *)hippyTag { @@ -293,9 +294,9 @@ - (instancetype)initWithFrame:(CGRect)frame _bridge = bridge; self.hippyTag = hippyTag; - // FIXME: HippyTouchHandler - // _touchHandler = [[HippyTouchHandler alloc] initWithRootView:self bridge:bridge]; - // [self addGestureRecognizer:_touchHandler]; + // 添加Hippy自定义手势识别器,用于管理手势事件,并将其发送至js端。 + _touchHandler = [[HippyTouchHandler alloc] initWithRootView:self bridge:bridge]; + [self addGestureRecognizer:_touchHandler]; self.layer.backgroundColor = NULL; _startTimpStamp = CACurrentMediaTime() * 1000; diff --git a/renderer/native/ios/renderer/NativeRenderScrollProtocol.h b/renderer/native/ios/renderer/HippyScrollProtocol.h similarity index 94% rename from renderer/native/ios/renderer/NativeRenderScrollProtocol.h rename to renderer/native/ios/renderer/HippyScrollProtocol.h index 07294c07581..68a1ae93b85 100644 --- a/renderer/native/ios/renderer/NativeRenderScrollProtocol.h +++ b/renderer/native/ios/renderer/HippyScrollProtocol.h @@ -22,7 +22,7 @@ #import -@protocol NativeRenderScrollProtocol +@protocol HippyScrollProtocol @required diff --git a/renderer/native/ios/renderer/HippyUIManager.h b/renderer/native/ios/renderer/HippyUIManager.h index 1fa540a5a14..79a1df8e151 100644 --- a/renderer/native/ios/renderer/HippyUIManager.h +++ b/renderer/native/ios/renderer/HippyUIManager.h @@ -25,6 +25,7 @@ #import "HippyInvalidating.h" #import "NativeRenderDefines.h" #import "HippyBridgeModule.h" +#import "HippyCustomTouchHandlerProtocol.h" #include #include #include @@ -73,15 +74,15 @@ class HippyValue; /** * Gets the view associated with a hippyTag. */ -- (UIView *)viewForComponentTag:(NSNumber *)componentTag - onRootTag:(NSNumber *)rootTag; +- (UIView *)viewForHippyTag:(NSNumber *)hippyTag + onRootTag:(NSNumber *)rootTag; /** * Get the shadow view associated with a hippyTag */ -- (HippyShadowView *)renderObjectForcomponentTag:(NSNumber *)componentTag - onRootTag:(NSNumber *)rootTag; +- (HippyShadowView *)shadowViewForHippyTag:(NSNumber *)hippyTag + onRootTag:(NSNumber *)rootTag; /** * Update the frame of a view. This might be in response to a screen rotation @@ -262,4 +263,8 @@ class HippyValue; /// The current HippyUIManager instance @property (nonatomic, readonly) HippyUIManager *uiManager; +/// A custom touch handler for gesture special processing +/// You can use it when you need to modify Hippy's default gesture handling logic +@property (nonatomic, strong, readonly) id customTouchHandler; + @end diff --git a/renderer/native/ios/renderer/HippyUIManager.mm b/renderer/native/ios/renderer/HippyUIManager.mm index a8fdb9d312f..670914023b8 100644 --- a/renderer/native/ios/renderer/HippyUIManager.mm +++ b/renderer/native/ios/renderer/HippyUIManager.mm @@ -270,18 +270,17 @@ - (HippyComponentMap *)viewRegistry { - (__kindof UIView *)viewFromRenderViewTag:(NSNumber *)componentTag onRootTag:(NSNumber *)rootTag { - return [self viewForComponentTag:componentTag onRootTag:rootTag]; + return [self viewForHippyTag:componentTag onRootTag:rootTag]; } -- (UIView *)viewForComponentTag:(NSNumber *)componentTag - onRootTag:(NSNumber *)rootTag { +- (UIView *)viewForHippyTag:(NSNumber *)hippyTag onRootTag:(NSNumber *)rootTag { AssertMainQueue(); - return [_viewRegistry componentForTag:componentTag onRootTag:rootTag]; + return [_viewRegistry componentForTag:hippyTag onRootTag:rootTag]; } -- (HippyShadowView *)renderObjectForcomponentTag:(NSNumber *)componentTag +- (HippyShadowView *)shadowViewForHippyTag:(NSNumber *)hippyTag onRootTag:(NSNumber *)rootTag { - return [_shadowViewRegistry componentForTag:componentTag onRootTag:rootTag]; + return [_shadowViewRegistry componentForTag:hippyTag onRootTag:rootTag]; } - (std::weak_ptr)renderManager { @@ -444,7 +443,7 @@ - (void)purgeChildren:(NSArray> *)children - (void)purgeViewsFromComponentTags:(NSArray *)componentTags onRootTag:(NSNumber *)rootTag { for (NSNumber *componentTag in componentTags) { - UIView *view = [self viewForComponentTag:componentTag onRootTag:rootTag]; + UIView *view = [self viewForHippyTag:componentTag onRootTag:rootTag]; HippyComponentMap *componentMap = _viewRegistry; NativeRenderTraverseViewNodes(view, ^(id subview) { NSAssert(![subview isHippyRootView], @"Root views should not be unregistered"); @@ -555,7 +554,7 @@ - (UIView *)createViewByComponentData:(HippyComponentData *)componentData rootTag:(NSNumber *)rootTag properties:(NSDictionary *)props viewName:(NSString *)viewName { - UIView *view = [self viewForComponentTag:componentTag onRootTag:rootTag]; + UIView *view = [self viewForHippyTag:componentTag onRootTag:rootTag]; BOOL canBeRetrievedFromCache = YES; if (view && [view respondsToSelector:@selector(canBeRetrievedFromViewCache)]) { canBeRetrievedFromCache = [view canBeRetrievedFromViewCache]; @@ -1068,8 +1067,8 @@ - (void)registerExtraComponent:(NSArray *)extraComponents { _extraComponents = extraComponents; } -#pragma mark - -#pragma mark Event Handler + +#pragma mark - Event Handler - (void)addEventName:(const std::string &)name forDomNodeId:(int32_t)node_id onRootNode:(std::weak_ptr)rootNode { @@ -1078,7 +1077,7 @@ - (void)addEventName:(const std::string &)name forDomNodeId:(int32_t)node_id return; } int32_t root_id = strongRootNode->GetId(); - HippyShadowView *renderObject = [self renderObjectForcomponentTag:@(node_id) onRootTag:@(root_id)]; + HippyShadowView *renderObject = [self shadowViewForHippyTag:@(node_id) onRootTag:@(root_id)]; [renderObject addEventName:name]; if (name == hippy::kClickEvent) { [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { @@ -1136,6 +1135,7 @@ - (void)addEventName:(const std::string &)name forDomNodeId:(int32_t)node_id } } +/// Called when creating view from shadowView - (void)addEventNameInMainThread:(const std::string &)name forDomNodeId:(int32_t)node_id onRootNode:(std::weak_ptr)rootNode { @@ -1163,14 +1163,14 @@ - (void)addClickEventListenerForView:(int32_t)componentTag onRootNode:(std::weak return; } int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForComponentTag:@(componentTag) onRootTag:@(root_id)]; + UIView *view = [self viewForHippyTag:@(componentTag) onRootTag:@(root_id)]; if (view) { __weak id weakSelf = self; - [view addViewEvent:NativeRenderViewEventTypeClick eventListener:^(CGPoint point, - BOOL canCapture, - BOOL canBubble, - BOOL canBePreventedInCapture, - BOOL canBePreventedInBubbling) { + OnTouchEventHandler eventListener = ^(CGPoint point, + BOOL canCapture, + BOOL canBubble, + BOOL canBePreventedInCapture, + BOOL canBePreventedInBubbling) { id strongSelf = weakSelf; if (strongSelf) { [strongSelf domNodeForComponentTag:componentTag onRootNode:rootNode resultNode:^(std::shared_ptr node) { @@ -1183,7 +1183,9 @@ - (void)addClickEventListenerForView:(int32_t)componentTag onRootNode:(std::weak } }]; } - }]; + }; +// [view addViewEvent:NativeRenderViewEventTypeClick eventListener:eventListener]; + [view setOnClick:eventListener]; } else { } @@ -1196,14 +1198,14 @@ - (void)addLongClickEventListenerForView:(int32_t)componentTag onRootNode:(std:: return; } int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForComponentTag:@(componentTag) onRootTag:@(root_id)]; + UIView *view = [self viewForHippyTag:@(componentTag) onRootTag:@(root_id)]; if (view) { __weak id weakSelf = self; - [view addViewEvent:NativeRenderViewEventTypeLongClick eventListener:^(CGPoint point, - BOOL canCapture, - BOOL canBubble, - BOOL canBePreventedInCapture, - BOOL canBePreventedInBubbling) { + OnTouchEventHandler eventListener = ^(CGPoint point, + BOOL canCapture, + BOOL canBubble, + BOOL canBePreventedInCapture, + BOOL canBePreventedInBubbling) { id strongSelf = weakSelf; if (strongSelf) { [strongSelf domNodeForComponentTag:componentTag onRootNode:rootNode resultNode:^(std::shared_ptr node) { @@ -1216,7 +1218,10 @@ - (void)addLongClickEventListenerForView:(int32_t)componentTag onRootNode:(std:: } }]; } - }]; + }; + +// [view addViewEvent:NativeRenderViewEventTypeLongClick eventListener:]; + [view setOnLongClick:eventListener]; } else { } @@ -1231,16 +1236,15 @@ - (void)addPressEventListenerForType:(const std::string &)type } int32_t root_id = strongRootNode->GetId(); AssertMainQueue(); - UIView *view = [self viewForComponentTag:@(componentTag) onRootTag:@(root_id)]; - NativeRenderViewEventType eventType = hippy::kPressIn == type ? NativeRenderViewEventType::NativeRenderViewEventTypePressIn : NativeRenderViewEventType::NativeRenderViewEventTypePressOut; + UIView *view = [self viewForHippyTag:@(componentTag) onRootTag:@(root_id)]; if (view) { std::string block_type = type; __weak id weakSelf = self; - [view addViewEvent:eventType eventListener:^(CGPoint point, - BOOL canCapture, - BOOL canBubble, - BOOL canBePreventedInCapture, - BOOL canBePreventedInBubbling) { + OnTouchEventHandler eventListener = ^(CGPoint point, + BOOL canCapture, + BOOL canBubble, + BOOL canBePreventedInCapture, + BOOL canBePreventedInBubbling) { id strongSelf = weakSelf; if (strongSelf) { [strongSelf domNodeForComponentTag:componentTag onRootNode:rootNode resultNode:^(std::shared_ptr node) { @@ -1253,9 +1257,12 @@ - (void)addPressEventListenerForType:(const std::string &)type } }]; } - }]; - } - else { + }; + if (hippy::kPressIn == type) { + [view setOnPressIn:eventListener]; + } else if (hippy::kPressOut == type) { + [view setOnPressOut:eventListener]; + } } } @@ -1268,26 +1275,15 @@ - (void)addTouchEventListenerForType:(const std::string &)type return; } int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForComponentTag:@(componentTag) onRootTag:@(root_id)]; + UIView *view = [self viewForHippyTag:@(componentTag) onRootTag:@(root_id)]; if (view) { - // todo 默认值应该有个值代表未知 - NativeRenderViewEventType event_type = NativeRenderViewEventType::NativeRenderViewEventTypeTouchStart; - if (type == hippy::kTouchStartEvent) { - event_type = NativeRenderViewEventType::NativeRenderViewEventTypeTouchStart; - } else if (type == hippy::kTouchMoveEvent) { - event_type = NativeRenderViewEventType::NativeRenderViewEventTypeTouchMove; - } else if (type == hippy::kTouchEndEvent) { - event_type = NativeRenderViewEventType::NativeRenderViewEventTypeTouchEnd; - } else if (type == hippy::kTouchCancelEvent) { - event_type = NativeRenderViewEventType::NativeRenderViewEventTypeTouchCancel; - } const std::string type_ = type; __weak id weakSelf = self; - [view addViewEvent:event_type eventListener:^(CGPoint point, - BOOL canCapture, - BOOL canBubble, - BOOL canBePreventedInCapture, - BOOL canBePreventedInBubbling) { + OnTouchEventHandler eventListener = ^(CGPoint point, + BOOL canCapture, + BOOL canBubble, + BOOL canBePreventedInCapture, + BOOL canBePreventedInBubbling) { id strongSelf = weakSelf; if (strongSelf) { [strongSelf domNodeForComponentTag:componentTag onRootNode:rootNode resultNode:^(std::shared_ptr node) { @@ -1303,46 +1299,24 @@ - (void)addTouchEventListenerForType:(const std::string &)type } }]; } - }]; - } - else { + }; + if (type == hippy::kTouchStartEvent) { + [view setOnTouchDown:eventListener]; + } else if (type == hippy::kTouchMoveEvent) { + [view setOnTouchMove:eventListener]; + } else if (type == hippy::kTouchEndEvent) { + [view setOnTouchEnd:eventListener]; + } else if (type == hippy::kTouchCancelEvent) { + [view setOnTouchCancel:eventListener]; + } } } - (void)addShowEventListenerForType:(const std::string &)type forView:(int32_t)componentTag onRootNode:(std::weak_ptr)rootNode { - AssertMainQueue(); - auto strongRootNode = rootNode.lock(); - if (!strongRootNode) { - return; - } - int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForComponentTag:@(componentTag) onRootTag:@(root_id)]; - if (view) { - NativeRenderViewEventType event_type = hippy::kShowEvent == type ? NativeRenderViewEventTypeShow : NativeRenderViewEventTypeDismiss; - __weak id weakSelf = self; - std::string type_ = type; - [view addViewEvent:event_type eventListener:^(CGPoint point, - BOOL canCapture, - BOOL canBubble, - BOOL canBePreventedInCapture, - BOOL canBePreventedInBubbling) { - id strongSelf = weakSelf; - if (strongSelf) { - [strongSelf domNodeForComponentTag:componentTag onRootNode:rootNode resultNode:^(std::shared_ptr node) { - if (node) { - std::shared_ptr domValue = std::make_shared(true); - auto event = std::make_shared(type_, node, canCapture, canBubble, domValue); - node->HandleEvent(event); - [strongSelf domEventDidHandle:type_ forNode:componentTag onRoot:root_id]; - } - }]; - } - }]; - } - else { - } + // Note: not implemented + // iOS do not have these event. } - (void)removeEventName:(const std::string &)eventName @@ -1352,20 +1326,9 @@ - (void)removeEventName:(const std::string &)eventName if (!strongRootNode) { return; } - int32_t root_id = strongRootNode->GetId(); + int32_t componentTag = node_id; - if (eventName == hippy::kClickEvent || - eventName ==hippy::kLongClickEvent || - eventName == hippy::kTouchStartEvent || eventName == hippy::kTouchMoveEvent || - eventName == hippy::kTouchEndEvent || eventName == hippy::kTouchCancelEvent || - eventName == hippy::kShowEvent || eventName == hippy::kDismissEvent || - eventName == hippy::kPressIn || eventName == hippy::kPressOut) { - std::string name_ = eventName; - [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { - UIView *view = [uiManager viewForComponentTag:@(componentTag) onRootTag:@(root_id)]; - [view removeViewEvent:viewEventTypeFromName(name_.c_str())]; - }]; - } else if (eventName == kVSyncKey) { + if (eventName == kVSyncKey) { std::string name_ = eventName; [self domNodeForComponentTag:node_id onRootNode:rootNode resultNode:^(std::shared_ptr node) { if (node) { @@ -1396,7 +1359,7 @@ - (void)addPropertyEvent:(const std::string &)name forDomNode:(int32_t)node_id return; } int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForComponentTag:@(node_id) onRootTag:@(root_id)]; + UIView *view = [self viewForHippyTag:@(node_id) onRootTag:@(root_id)]; if (view) { std::string name_ = name; NSDictionary *componentDataByName = [_componentDataByName copy]; @@ -1521,6 +1484,15 @@ - (HippyUIManager *)uiManager { return nil; } +- (id)customTouchHandler { + return objc_getAssociatedObject(self, @selector(customTouchHandler)); +} + +- (void)setCustomTouchHandler:(id)customTouchHandler { + objc_setAssociatedObject(self, @selector(customTouchHandler), customTouchHandler, OBJC_ASSOCIATION_RETAIN); +} + + @end diff --git a/renderer/native/ios/renderer/component/image/NativeRenderImageView+NativeRenderTouchesImplementation.mm b/renderer/native/ios/renderer/component/image/NativeRenderImageView+NativeRenderTouchesImplementation.mm deleted file mode 100644 index 590ccf41198..00000000000 --- a/renderer/native/ios/renderer/component/image/NativeRenderImageView+NativeRenderTouchesImplementation.mm +++ /dev/null @@ -1,363 +0,0 @@ -/*! - * iOS SDK - * - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed 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 "NativeRenderImageView+NativeRenderTouchesImplementation.h" -#import "UIView+DomEvent.h" -#import "UIView+Hippy.h" -#import "UIEvent+TouchResponder.h" -#import "objc/runtime.h" - -@implementation NativeRenderImageView (NativeRenderTouchesImplementation) - -#pragma mark Setter & Getter -- (NSMutableDictionary *)touchesEvents { - NSMutableDictionary *events = objc_getAssociatedObject(self, @selector(touchesEvents)); - if (!events) { - events = [NSMutableDictionary dictionaryWithCapacity:4]; - objc_setAssociatedObject(self, @selector(touchesEvents), events, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - return events; -} - -- (UITapGestureRecognizer *)tapGestureRecognizer { - UITapGestureRecognizer *tap = objc_getAssociatedObject(self, @selector(tapGestureRecognizer)); - if (!tap) { - tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickEvent)]; - tap.cancelsTouchesInView = NO; - tap.delaysTouchesEnded = NO; - objc_setAssociatedObject(self, @selector(tapGestureRecognizer), tap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - return tap; -} - -- (void)removeTapGestureRecognizerIfExists { - UITapGestureRecognizer *tap = objc_getAssociatedObject(self, @selector(tapGestureRecognizer)); - if (tap) { - objc_setAssociatedObject(self, @selector(tapGestureRecognizer), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [self removeGestureRecognizer:tap]; - } -} - -- (void)removeLongGestureRecognizerIfExists { - UILongPressGestureRecognizer *longPress = objc_getAssociatedObject(self, @selector(longGestureRecognizer)); - if (longPress) { - objc_setAssociatedObject(self, @selector(longGestureRecognizer), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [self removeGestureRecognizer:longPress]; - } -} - -- (UILongPressGestureRecognizer *)longGestureRecognizer { - UILongPressGestureRecognizer *longPress = objc_getAssociatedObject(self, @selector(longGestureRecognizer)); - if (!longPress) { - longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongClickEvent)]; - longPress.cancelsTouchesInView = NO; - objc_setAssociatedObject(self, @selector(longGestureRecognizer), longPress, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - return longPress; -} - -- (void)setPressInEventEnabled:(BOOL)enabled { - objc_setAssociatedObject(self, @selector(pressInEventEnabled), @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (BOOL)pressInEventEnabled { - NSNumber *enabled = objc_getAssociatedObject(self, @selector(pressInEventEnabled)); - return [enabled boolValue]; -} - -- (NSTimer *)enablePressInTimer { - NSTimer *pressInTimer = [NSTimer scheduledTimerWithTimeInterval:.1f target:self selector:@selector(handlePressInEvent) userInfo:nil repeats:NO]; - objc_setAssociatedObject(self, @selector(enablePressInTimer), pressInTimer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return pressInTimer; -} - -- (void)disablePressInTimer { - NSTimer *timer = objc_getAssociatedObject(self, @selector(enablePressInTimer)); - [timer invalidate]; - objc_setAssociatedObject(self, @selector(enablePressInTimer), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -#pragma mark NativeRenderTouchesProtol Implementation -- (void)addViewEvent:(NativeRenderViewEventType)touchEvent eventListener:(OnTouchEventHandler)listener { - self.userInteractionEnabled = YES; - switch (touchEvent) { - case NativeRenderViewEventTypeTouchStart: - case NativeRenderViewEventTypeTouchMove: - case NativeRenderViewEventTypeTouchEnd: - case NativeRenderViewEventTypeTouchCancel: - [self setTouchEventListener:listener forEvent:touchEvent]; - break; - case NativeRenderViewEventTypeClick: - [self addClickEventListener:listener]; - break; - case NativeRenderViewEventTypeLongClick: - [self addLongClickEventListener:listener]; - break; - case NativeRenderViewEventTypePressIn: - [self addPressInEventListener:listener]; - break; - case NativeRenderViewEventTypePressOut: - [self addPressOutEventListener:listener]; - break; - default: - break; - } -} - -- (OnTouchEventHandler)eventListenerForEventType:(NativeRenderViewEventType)eventType { - return [[self touchesEvents] objectForKey:@(eventType)]; -} - -- (void)removeViewEvent:(NativeRenderViewEventType)touchEvent { - [[self touchesEvents] removeObjectForKey:@(touchEvent)]; - if (NativeRenderViewEventTypeClick == touchEvent) { - [self removeTapGestureRecognizerIfExists]; - } - else if (NativeRenderViewEventTypeLongClick == touchEvent) { - [self removeLongGestureRecognizerIfExists]; - } - else if (NativeRenderViewEventTypePressIn == touchEvent) { - if ([self pressInEventEnabled]) { - [self disablePressInTimer]; - [self setPressInEventEnabled:NO]; - } - } -} - -#pragma mark Touch Event Listener Add Methods -- (void)setTouchEventListener:(OnTouchEventHandler)eventListener forEvent:(NativeRenderViewEventType)event { - if (eventListener) { - [[self touchesEvents] setObject:eventListener forKey:@(event)]; - } -} - -- (void)addClickEventListener:(OnTouchEventHandler)eventListener { - [self removeTapGestureRecognizerIfExists]; - UITapGestureRecognizer *tap = [self tapGestureRecognizer]; - [self addGestureRecognizer:tap]; - [[self touchesEvents] setObject:eventListener forKey:@(NativeRenderViewEventTypeClick)]; -} - -- (void)addLongClickEventListener:(OnTouchEventHandler)eventListener { - [self removeLongGestureRecognizerIfExists]; - UILongPressGestureRecognizer *longPress = [self longGestureRecognizer]; - [self addGestureRecognizer:longPress]; - [[self touchesEvents] setObject:eventListener forKey:@(NativeRenderViewEventTypeLongClick)]; -} - -- (void)addPressInEventListener:(OnTouchEventHandler)eventListener { - [self disablePressInTimer]; - [self setPressInEventEnabled:YES]; - [[self touchesEvents] setObject:eventListener forKey:@(NativeRenderViewEventTypePressIn)]; -} - -- (void)addPressOutEventListener:(OnTouchEventHandler)eventListener { - [[self touchesEvents] setObject:eventListener forKey:@(NativeRenderViewEventTypePressOut)]; -} - -#pragma mark Touches Event Handler -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - if ([self pressInEventEnabled]) { - [self enablePressInTimer]; - } - if ([self tryToHandleEvent:event forEventType:NativeRenderViewEventTypeTouchStart]) { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeTouchStart]; - if (listener) { - UITouch *touch = [touches anyObject]; - UIView *rootView = [self NativeRenderRootView]; - CGPoint point = [touch locationInView:rootView]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeTouchStart); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } - [super touchesBegan:touches withEvent:event]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - if ([self pressInEventEnabled]) { - [self disablePressInTimer]; - } - [self handlePressOutEvent]; - if ([self tryToHandleEvent:event forEventType:NativeRenderViewEventTypeTouchEnd]) { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeTouchEnd]; - if (listener) { - UITouch *touch = [touches anyObject]; - UIView *rootView = [self NativeRenderRootView]; - CGPoint point = [touch locationInView:rootView]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeTouchEnd); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } - [super touchesEnded:touches withEvent:event]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - if ([self tryToHandleEvent:event forEventType:NativeRenderViewEventTypeTouchMove]) { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeTouchMove]; - if (listener) { - [self handlePressOutEvent]; - UITouch *touch = [touches anyObject]; - UIView *rootView = [self NativeRenderRootView]; - CGPoint point = [touch locationInView:rootView]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeTouchMove); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } - [super touchesMoved:touches withEvent:event]; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - if ([self pressInEventEnabled]) { - [self disablePressInTimer]; - } - [self handlePressOutEvent]; - if ([self tryToHandleEvent:event forEventType:NativeRenderViewEventTypeTouchCancel]) { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeTouchCancel]; - if (listener) { - UITouch *touch = [touches anyObject]; - UIView *rootView = [self NativeRenderRootView]; - CGPoint point = [touch locationInView:rootView]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeTouchCancel); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } - [super touchesCancelled:touches withEvent:event]; -} - -- (BOOL)tryToHandleEvent:(UIEvent *)event forEventType:(NativeRenderViewEventType)eventType { - id responder = [event responderForType:eventType]; - if (self == responder) { - return YES; - } - if (nil == responder) { - // assume first responder is self - UIView *responder = nil; - // find out is there any parent view who can handle `eventType` and `onInterceptTouchEvent` is YES - UIView *testingView = self; - while (testingView) { - OnTouchEventHandler handler = [testingView eventListenerForEventType:eventType]; - if (!responder && handler) { - responder = testingView; - } - BOOL onInterceptTouchEvent = testingView.onInterceptTouchEvent; - if (handler && onInterceptTouchEvent) { - responder = testingView; - } - testingView = [testingView parentComponent]; - } - // set first responder for `eventType` - if (responder) { - [event setResponder:responder forType:eventType]; - } - else { - [event setResponder:[NSNull null] forType:eventType]; - } - if (responder == self) { - return YES; - } - } - return NO; -} - -- (void)handleClickEvent { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeClick]; - if (listener) { - UITapGestureRecognizer *tap = objc_getAssociatedObject(self, @selector(tapGestureRecognizer)); - CGPoint point = [tap locationInView:[self NativeRenderRootView]]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeClick); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } -} - -- (void)handleLongClickEvent { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeLongClick]; - if (listener) { - UILongPressGestureRecognizer *longPress = objc_getAssociatedObject(self, @selector(longGestureRecognizer)); - if (longPress.state == UIGestureRecognizerStateBegan) { - CGPoint point = [longPress locationInView:[self NativeRenderRootView]]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeLongClick); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } -} - -- (void)handlePressInEvent { - [self disablePressInTimer]; - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypePressIn]; - if (listener) { - const char *name = viewEventNameFromType(NativeRenderViewEventTypePressIn); - listener(CGPointZero, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } -} - -- (void)handlePressOutEvent { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypePressOut]; - if (listener) { - const char *name = viewEventNameFromType(NativeRenderViewEventTypePressOut); - listener(CGPointZero, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } -} - -- (void)resetAllEvents { - [self removeViewEvent:NativeRenderViewEventTypeTouchStart]; - [self removeViewEvent:NativeRenderViewEventTypeTouchEnd]; - [self removeViewEvent:NativeRenderViewEventTypeTouchMove]; - [self removeViewEvent:NativeRenderViewEventTypeTouchCancel]; - [self removeViewEvent:NativeRenderViewEventTypePressIn]; - [self removeViewEvent:NativeRenderViewEventTypePressOut]; - [self removeViewEvent:NativeRenderViewEventTypeClick]; - [self removeViewEvent:NativeRenderViewEventTypeLongClick]; -} - -@end diff --git a/renderer/native/ios/renderer/component/smartViewPager/NativeRenderSmartViewPagerView.mm b/renderer/native/ios/renderer/component/smartViewPager/NativeRenderSmartViewPagerView.mm index 3df57183838..83cad818a0a 100644 --- a/renderer/native/ios/renderer/component/smartViewPager/NativeRenderSmartViewPagerView.mm +++ b/renderer/native/ios/renderer/component/smartViewPager/NativeRenderSmartViewPagerView.mm @@ -27,7 +27,7 @@ #import "HippyUIManager.h" #import "HippyShadowView.h" #import "NativeRenderSmartViewPagerView.h" -#import "NativeRenderScrollProtocol.h" +#import "HippyScrollProtocol.h" #import "UIView+MountEvent.h" #import "UIView+Render.h" #import "UIView+Hippy.h" diff --git a/renderer/native/ios/renderer/component/view/HippyView.m b/renderer/native/ios/renderer/component/view/HippyView.m index 039080abc7b..0b2890e839a 100644 --- a/renderer/native/ios/renderer/component/view/HippyView.m +++ b/renderer/native/ios/renderer/component/view/HippyView.m @@ -25,7 +25,6 @@ #import "HippyBorderDrawing.h" #import "NativeRenderGradientObject.h" #import "HippyView.h" -#import "UIEvent+TouchResponder.h" #import "UIView+DomEvent.h" #import "UIView+Hippy.h" diff --git a/renderer/native/ios/renderer/component/view/HippyViewEventProtocol.h b/renderer/native/ios/renderer/component/view/HippyViewEventProtocol.h new file mode 100644 index 00000000000..3fe4d353a9b --- /dev/null +++ b/renderer/native/ios/renderer/component/view/HippyViewEventProtocol.h @@ -0,0 +1,60 @@ +/*! + * iOS SDK + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed 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. + */ + +#ifndef HPViewEventProtocol_h +#define HPViewEventProtocol_h + +#import +#import "NativeRenderTouchesProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol HippyViewEventProtocol + +@property (nonatomic, copy) OnTouchEventHandler onClick; +@property (nonatomic, copy) OnTouchEventHandler onLongClick; +@property (nonatomic, copy) OnTouchEventHandler onPressIn; +@property (nonatomic, copy) OnTouchEventHandler onPressOut; + +@property (nonatomic, copy) OnTouchEventHandler onTouchDown; +@property (nonatomic, copy) OnTouchEventHandler onTouchMove; +@property (nonatomic, copy) OnTouchEventHandler onTouchEnd; +@property (nonatomic, copy) OnTouchEventHandler onTouchCancel; + +//@property (nonatomic, copy) HippyDirectEventBlock onAttachedToWindow; +//@property (nonatomic, copy) HippyDirectEventBlock onDetachedFromWindow; + +@property (nonatomic, assign) BOOL onInterceptTouchEvent; +@property (nonatomic, assign) BOOL onInterceptPullUpEvent; + +@end + +@protocol HippyViewTouchHandlerProtocol + +- (BOOL)interceptTouchEvent; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* HPViewEventProtocol_h */ + diff --git a/renderer/native/ios/renderer/component/view/NativeRenderTouchesProtocol.h b/renderer/native/ios/renderer/component/view/NativeRenderTouchesProtocol.h index 1fb3887149d..d2def49c4d8 100644 --- a/renderer/native/ios/renderer/component/view/NativeRenderTouchesProtocol.h +++ b/renderer/native/ios/renderer/component/view/NativeRenderTouchesProtocol.h @@ -21,7 +21,6 @@ */ #import -#import "NativeRenderViewEventType.h" NS_ASSUME_NONNULL_BEGIN @@ -37,25 +36,7 @@ typedef void(^OnTouchEventHandler)(CGPoint point, */ @protocol NativeRenderTouchesProtocol -/** - * Add an event for a view - * @param touchEvent event type - * @param listener event handle block - */ -- (void)addViewEvent:(NativeRenderViewEventType)touchEvent eventListener:(OnTouchEventHandler)listener; - -/** - * Get event handle block with event type - * @param eventType event type - * @return event handle block for eventType - */ -- (OnTouchEventHandler)eventListenerForEventType:(NativeRenderViewEventType)eventType; -/** - * Remove event handle block - * @param touchEvent event type - */ -- (void)removeViewEvent:(NativeRenderViewEventType)touchEvent; /** * Indicate if event can be prevented in capturing process @@ -85,8 +66,6 @@ typedef void(^OnTouchEventHandler)(CGPoint point, */ - (BOOL)canBubble:(const char *)name; -- (void)resetAllEvents; - @end NS_ASSUME_NONNULL_END diff --git a/renderer/native/ios/renderer/component/view/NativeRenderTouchesView.m b/renderer/native/ios/renderer/component/view/NativeRenderTouchesView.m index 049074aa0c3..303c2f9d94c 100644 --- a/renderer/native/ios/renderer/component/view/NativeRenderTouchesView.m +++ b/renderer/native/ios/renderer/component/view/NativeRenderTouchesView.m @@ -21,52 +21,14 @@ */ #import "NativeRenderTouchesView.h" -#import "UIEvent+TouchResponder.h" #import "UIView+DomEvent.h" #import "UIView+MountEvent.h" #import "UIView+Hippy.h" #import "objc/runtime.h" -@interface NativeRenderTouchesView () { - NSMutableDictionary *_touchesEvents; - UITapGestureRecognizer *_tapGestureRecognizer; - UILongPressGestureRecognizer *_longGestureRecognizer; - NSTimer *_pressInTimer; - BOOL _pressInEventEnabled; -} - -@end @implementation NativeRenderTouchesView -#pragma mark Life Cycles -- (instancetype)init { - self = [super init]; - if (self) { - [self setDefaultProperties]; - } - return self; -} - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self setDefaultProperties]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - [self setDefaultProperties]; - } - return self; -} - -- (void)setDefaultProperties { - self.backgroundColor = [UIColor clearColor]; -} - (void)didMoveToSuperview { [super didMoveToSuperview]; @@ -78,66 +40,6 @@ - (void)didMoveToSuperview { } } -#pragma mark Setter & Getter -- (NSMutableDictionary *)touchesEvents { - if (!_touchesEvents) { - _touchesEvents = [NSMutableDictionary dictionaryWithCapacity:4]; - } - return _touchesEvents; -} - -#pragma mark NativeRenderTouchesProtol Implementation -- (void)addViewEvent:(NativeRenderViewEventType)touchEvent eventListener:(OnTouchEventHandler)listener { - switch (touchEvent) { - case NativeRenderViewEventTypeTouchStart: - case NativeRenderViewEventTypeTouchMove: - case NativeRenderViewEventTypeTouchEnd: - case NativeRenderViewEventTypeTouchCancel: - [self setTouchEventListener:listener forEvent:touchEvent]; - break; - case NativeRenderViewEventTypeClick: - [self addClickEventListener:listener]; - break; - case NativeRenderViewEventTypeLongClick: - [self addLongClickEventListener:listener]; - break; - case NativeRenderViewEventTypePressIn: - [self addPressInEventListener:listener]; - break; - case NativeRenderViewEventTypePressOut: - [self addPressOutEventListener:listener]; - break; - default: - break; - } -} - -- (OnTouchEventHandler)eventListenerForEventType:(NativeRenderViewEventType)eventType { - return [_touchesEvents objectForKey:@(eventType)]; -} - -- (void)removeViewEvent:(NativeRenderViewEventType)touchEvent { - [_touchesEvents removeObjectForKey:@(touchEvent)]; - if (NativeRenderViewEventTypeClick == touchEvent) { - if (_tapGestureRecognizer) { - [self removeGestureRecognizer:_tapGestureRecognizer]; - _tapGestureRecognizer = nil; - } - } - else if (NativeRenderViewEventTypeLongClick == touchEvent) { - if (_longGestureRecognizer) { - [self removeGestureRecognizer:_longGestureRecognizer]; - _longGestureRecognizer = nil; - } - } - else if (NativeRenderViewEventTypePressIn == touchEvent) { - if (_pressInEventEnabled) { - [_pressInTimer invalidate]; - _pressInTimer = nil; - _pressInEventEnabled = NO; - } - } -} - (void)setPointerEvents:(NativeRenderPointerEvents)pointerEvents { _pointerEvents = pointerEvents; @@ -191,238 +93,5 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { } } -#pragma mark Touch Event Listener Add Methods -- (void)setTouchEventListener:(OnTouchEventHandler)eventListener forEvent:(NativeRenderViewEventType)event { - if (eventListener) { - [[self touchesEvents] setObject:eventListener forKey:@(event)]; - } -} - -- (void)addClickEventListener:(OnTouchEventHandler)eventListener { - if (_tapGestureRecognizer) { - return; - } - _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickEvent)]; - _tapGestureRecognizer.cancelsTouchesInView = NO; - _tapGestureRecognizer.delaysTouchesEnded = NO; - [self addGestureRecognizer:_tapGestureRecognizer]; - [[self touchesEvents] setObject:eventListener forKey:@(NativeRenderViewEventTypeClick)]; -} - -- (void)addLongClickEventListener:(OnTouchEventHandler)eventListener { - if (_longGestureRecognizer) { - return; - } - _longGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongClickEvent)]; - _longGestureRecognizer.cancelsTouchesInView = NO; - [self addGestureRecognizer:_longGestureRecognizer]; - [[self touchesEvents] setObject:eventListener forKey:@(NativeRenderViewEventTypeLongClick)]; -} - -- (void)addPressInEventListener:(OnTouchEventHandler)eventListener { - if (_pressInEventEnabled) { - return; - } - if (_pressInTimer) { - [_pressInTimer invalidate]; - } - _pressInEventEnabled = YES; - [[self touchesEvents] setObject:eventListener forKey:@(NativeRenderViewEventTypePressIn)]; -} - -- (void)addPressOutEventListener:(OnTouchEventHandler)eventListener { - [[self touchesEvents] setObject:eventListener forKey:@(NativeRenderViewEventTypePressOut)]; -} - -#pragma mark Touches Event Handler -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - if (_pressInEventEnabled) { - _pressInTimer = [NSTimer scheduledTimerWithTimeInterval:.1f target:self selector:@selector(handlePressInEvent) userInfo:nil repeats:NO]; - } - if ([self tryToHandleEvent:event forEventType:NativeRenderViewEventTypeTouchStart]) { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeTouchStart]; - if (listener) { - UITouch *touch = [touches anyObject]; - UIView *rootView = [self NativeRenderRootView]; - CGPoint point = [touch locationInView:rootView]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeTouchStart); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } - [super touchesBegan:touches withEvent:event]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - if (_pressInEventEnabled) { - [_pressInTimer invalidate]; - _pressInTimer = nil; - } - [self handlePressOutEvent]; - if ([self tryToHandleEvent:event forEventType:NativeRenderViewEventTypeTouchEnd]) { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeTouchEnd]; - if (listener) { - UITouch *touch = [touches anyObject]; - UIView *rootView = [self NativeRenderRootView]; - CGPoint point = [touch locationInView:rootView]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeTouchEnd); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } - [super touchesEnded:touches withEvent:event]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - if ([self tryToHandleEvent:event forEventType:NativeRenderViewEventTypeTouchMove]) { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeTouchMove]; - if (listener) { - [self handlePressOutEvent]; - UITouch *touch = [touches anyObject]; - UIView *rootView = [self NativeRenderRootView]; - CGPoint point = [touch locationInView:rootView]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeTouchMove); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } - [super touchesMoved:touches withEvent:event]; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - if (_pressInEventEnabled) { - [_pressInTimer invalidate]; - _pressInTimer = nil; - } - [self handlePressOutEvent]; - if ([self tryToHandleEvent:event forEventType:NativeRenderViewEventTypeTouchCancel]) { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeTouchCancel]; - if (listener) { - UITouch *touch = [touches anyObject]; - UIView *rootView = [self NativeRenderRootView]; - CGPoint point = [touch locationInView:rootView]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeTouchCancel); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } - [super touchesCancelled:touches withEvent:event]; -} - -- (BOOL)tryToHandleEvent:(UIEvent *)event forEventType:(NativeRenderViewEventType)eventType { - id responder = [event responderForType:eventType]; - if (self == responder) { - return YES; - } - if (nil == responder) { - // assume first responder is self - UIView *responder = nil; - // find out is there any parent view who can handle `eventType` and `onInterceptTouchEvent` is YES - UIView *testingView = self; - while (testingView) { - OnTouchEventHandler handler = [testingView eventListenerForEventType:eventType]; - if (!responder && handler) { - responder = testingView; - } - BOOL onInterceptTouchEvent = testingView.onInterceptTouchEvent; - if (handler && onInterceptTouchEvent) { - responder = testingView; - } - testingView = [testingView parentComponent]; - } - // set first responder for `eventType` - if (responder) { - [event setResponder:responder forType:eventType]; - } - else { - [event setResponder:[NSNull null] forType:eventType]; - } - if (responder == self) { - return YES; - } - } - return NO; -} - -- (void)handleClickEvent { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeClick]; - if (listener) { - CGPoint point = [_tapGestureRecognizer locationInView:[self NativeRenderRootView]]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeClick); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } -} - -- (void)handleLongClickEvent { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypeLongClick]; - if (listener) { - if (_longGestureRecognizer.state == UIGestureRecognizerStateBegan) { - CGPoint point = [_longGestureRecognizer locationInView:[self NativeRenderRootView]]; - const char *name = viewEventNameFromType(NativeRenderViewEventTypeLongClick); - listener(point, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } - } -} - -- (void)handlePressInEvent { - [_pressInTimer invalidate]; - _pressInTimer = nil; - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypePressIn]; - if (listener) { - const char *name = viewEventNameFromType(NativeRenderViewEventTypePressIn); - listener(CGPointZero, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } -} - -- (void)handlePressOutEvent { - OnTouchEventHandler listener = [self eventListenerForEventType:NativeRenderViewEventTypePressOut]; - if (listener) { - const char *name = viewEventNameFromType(NativeRenderViewEventTypePressOut); - listener(CGPointZero, - [self canCapture:name], - [self canBubble:name], - [self canBePreventedByInCapturing:name], - [self canBePreventInBubbling:name]); - } -} - -- (void)resetAllEvents { - [_touchesEvents removeAllObjects]; - if (_tapGestureRecognizer) { - [self removeGestureRecognizer:_tapGestureRecognizer]; - } - if (_longGestureRecognizer) { - [self removeGestureRecognizer:_longGestureRecognizer]; - } - if (_pressInEventEnabled || _pressInTimer) { - _pressInEventEnabled = NO; - [_pressInTimer invalidate]; - _pressInTimer = nil; - } -} @end diff --git a/renderer/native/ios/renderer/component/view/NativeRenderViewEventType.h b/renderer/native/ios/renderer/component/view/NativeRenderViewEventType.h deleted file mode 100644 index fc4b7bfa74a..00000000000 --- a/renderer/native/ios/renderer/component/view/NativeRenderViewEventType.h +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * iOS SDK - * - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed 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 "HippyDefines.h" - -typedef NS_ENUM(NSInteger, NativeRenderViewEventType) { - //touche event - NativeRenderViewEventTypeTouchStart, - NativeRenderViewEventTypeTouchMove, - NativeRenderViewEventTypeTouchEnd, - NativeRenderViewEventTypeTouchCancel, - - NativeRenderViewEventTypePressIn, - NativeRenderViewEventTypePressOut, - - NativeRenderViewEventLayout, - - //show event - NativeRenderViewEventTypeShow, - NativeRenderViewEventTypeDismiss, - - //click event - NativeRenderViewEventTypeClick, - NativeRenderViewEventTypeLongClick, - - NativeRenderViewEventTypeUnknown = -1, -}; - -HIPPY_EXTERN NativeRenderViewEventType viewEventTypeFromName(const char * _Nullable name); - -HIPPY_EXTERN const char *_Nullable viewEventNameFromType(NativeRenderViewEventType eventType); diff --git a/renderer/native/ios/renderer/component/view/NativeRenderViewEventType.mm b/renderer/native/ios/renderer/component/view/NativeRenderViewEventType.mm deleted file mode 100644 index 6484ff8bfdf..00000000000 --- a/renderer/native/ios/renderer/component/view/NativeRenderViewEventType.mm +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * iOS SDK - * - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed 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 "NativeRenderViewEventType.h" - -#include "dom/dom_listener.h" - -NativeRenderViewEventType viewEventTypeFromName(const char * _Nullable name) { - if (!name) { - return NativeRenderViewEventTypeUnknown; - } - NativeRenderViewEventType type = NativeRenderViewEventTypeUnknown; - if (0 == strcmp(hippy::kClickEvent, name)) { - type = NativeRenderViewEventTypeClick; - } - else if (0 == strcmp(hippy::kLongClickEvent, name)) { - type = NativeRenderViewEventTypeLongClick; - } - else if (0 == strcmp(hippy::kTouchStartEvent, name)) { - type = NativeRenderViewEventTypeTouchStart; - } - else if (0 == strcmp(hippy::kTouchMoveEvent, name)) { - type = NativeRenderViewEventTypeTouchMove; - } - else if (0 == strcmp(hippy::kTouchEndEvent, name)) { - type = NativeRenderViewEventTypeTouchEnd; - } - else if (0 == strcmp(hippy::kTouchCancelEvent, name)) { - type = NativeRenderViewEventTypeTouchCancel; - } - else if (0 == strcmp(hippy::kPressIn, name)) { - type = NativeRenderViewEventTypePressIn; - } - else if (0 == strcmp(hippy::kPressOut, name)) { - type = NativeRenderViewEventTypePressOut; - } - else if (0 == strcmp(hippy::kLayoutEvent, name)) { - type = NativeRenderViewEventLayout; - } - else if (0 == strcmp(hippy::kShowEvent, name)) { - type = NativeRenderViewEventTypeShow; - } - else if (0 == strcmp(hippy::kDismissEvent, name)) { - type = NativeRenderViewEventTypeDismiss; - } - return type; -} - -const char * _Nullable viewEventNameFromType(NativeRenderViewEventType eventType) { - const char *name = nullptr; - switch (eventType) { - case NativeRenderViewEventTypeTouchStart: - name = hippy::kTouchStartEvent; - break; - case NativeRenderViewEventTypeTouchMove: - name = hippy::kTouchMoveEvent; - break; - case NativeRenderViewEventTypeTouchEnd: - name = hippy::kTouchEndEvent; - break; - case NativeRenderViewEventTypeTouchCancel: - name = hippy::kTouchCancelEvent; - break; - case NativeRenderViewEventTypePressIn: - name = hippy::kPressIn; - break; - case NativeRenderViewEventTypePressOut: - name = hippy::kPressOut; - break; - case NativeRenderViewEventLayout: - name = hippy::kLayoutEvent; - break; - case NativeRenderViewEventTypeShow: - name = hippy::kShowEvent; - break; - case NativeRenderViewEventTypeDismiss: - name = hippy::kDismissEvent; - break; - case NativeRenderViewEventTypeClick: - name = hippy::kClickEvent; - break; - case NativeRenderViewEventTypeLongClick: - name = hippy::kLongClickEvent; - break; - default: - break; - } - return name; -} diff --git a/renderer/native/ios/renderer/component/view/UIEvent+TouchResponder.m b/renderer/native/ios/renderer/component/view/UIEvent+TouchResponder.m deleted file mode 100644 index af1a68533ab..00000000000 --- a/renderer/native/ios/renderer/component/view/UIEvent+TouchResponder.m +++ /dev/null @@ -1,49 +0,0 @@ -/*! - * iOS SDK - * - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed 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 "UIEvent+TouchResponder.h" -#import "objc/runtime.h" - -@implementation UIEvent (TouchResponder) - -- (NSMapTable *)respondersMap { - NSMapTable *map = objc_getAssociatedObject(self, _cmd); - if (!map) { - map = [NSMapTable strongToWeakObjectsMapTable]; - objc_setAssociatedObject(self, _cmd, map, OBJC_ASSOCIATION_RETAIN); - } - return map; -} - -- (void)setResponder:(__weak id)responder forType:(NativeRenderViewEventType)type { - [[self respondersMap] setObject:responder forKey:@(type)]; -} - -- (id)responderForType:(NativeRenderViewEventType)type { - return [[self respondersMap] objectForKey:@(type)]; -} - -- (void)removeAllResponders { - [[self respondersMap] removeAllObjects]; -} - -@end diff --git a/renderer/native/ios/renderer/component/view/UIView+DomEvent.h b/renderer/native/ios/renderer/component/view/UIView+DomEvent.h index 08a638bf7a1..4651140907e 100644 --- a/renderer/native/ios/renderer/component/view/UIView+DomEvent.h +++ b/renderer/native/ios/renderer/component/view/UIView+DomEvent.h @@ -38,7 +38,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, assign)BOOL onInterceptTouchEvent; -@property(nonatomic, readonly, copy)NSSet *propertyEventsName; /** * Add custom property event for view @@ -70,7 +69,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)didRemovePropertyEvent:(const char *)name; -- (void)removeAllPropertyEvents; @end diff --git a/renderer/native/ios/renderer/component/view/UIView+DomEvent.mm b/renderer/native/ios/renderer/component/view/UIView+DomEvent.mm index a82d9fd54a6..44d3f088791 100644 --- a/renderer/native/ios/renderer/component/view/UIView+DomEvent.mm +++ b/renderer/native/ios/renderer/component/view/UIView+DomEvent.mm @@ -24,24 +24,11 @@ #import #import "UIView+MountEvent.h" #import "UIView+Hippy.h" -#import "UIEvent+TouchResponder.h" #include "dom/dom_listener.h" @implementation UIView(DomEvent) -+ (void)load { - if (self == [UIView self]) { - Method originMethod = class_getInstanceMethod([UIView class], @selector(hitTest:withEvent:)); - Method exchangeMethod = class_getInstanceMethod([UIView class], @selector(hippy_domEvent_hitTest:withEvent:)); - method_exchangeImplementations(originMethod, exchangeMethod); - } -} - -- (UIView *)hippy_domEvent_hitTest:(CGPoint)point withEvent:(UIEvent *)event { - [event removeAllResponders]; - return [self hippy_domEvent_hitTest:point withEvent:event]; -} - (void)setOnInterceptTouchEvent:(BOOL)onInterceptTouchEvent { objc_setAssociatedObject(self, @selector(onInterceptTouchEvent), @(onInterceptTouchEvent), OBJC_ASSOCIATION_RETAIN); @@ -60,10 +47,6 @@ - (BOOL)onInterceptTouchEvent { return names; } -- (NSSet *)propertyEventsName { - return [[self _propertyEventsName] copy]; -} - static SEL SelectorFromCName(const char *name) { if (!name || strlen(name) < 1) { return nil; @@ -141,22 +124,9 @@ - (void)didRemovePropertyEvent:(const char *)name { [[self _propertyEventsName] removeObject:@(name)]; } -- (void)removeAllPropertyEvents { - NSSet *set = [self propertyEventsName]; - for (NSString *name in set) { - [self removePropertyEvent:[name UTF8String]]; - } -} #pragma mark NativeRenderTouchesProtocol Methods -- (void)addViewEvent:(NativeRenderViewEventType)touchEvent eventListener:(OnTouchEventHandler)listener {} - -- (OnTouchEventHandler)eventListenerForEventType:(NativeRenderViewEventType)eventType { - return NULL; -} -- (void)removeViewEvent:(NativeRenderViewEventType)touchEvent { -} - (BOOL)canBePreventedByInCapturing:(const char *)name { return NO; @@ -197,6 +167,4 @@ - (BOOL)canBubble:(const char *)name { return IsGestureEvent(name); } -- (void)resetAllEvents {} - @end diff --git a/renderer/native/ios/renderer/component/view/UIView+Hippy.h b/renderer/native/ios/renderer/component/view/UIView+Hippy.h index 18d19d9a9f9..0f455d6b5a2 100644 --- a/renderer/native/ios/renderer/component/view/UIView+Hippy.h +++ b/renderer/native/ios/renderer/component/view/UIView+Hippy.h @@ -22,10 +22,11 @@ #import #import "HippyComponent.h" +#import "HippyViewEventProtocol.h" @class HippyShadowView; -@interface UIView (Hippy) +@interface UIView (Hippy) /** * reset all hippy subviews diff --git a/renderer/native/ios/renderer/component/view/UIView+Hippy.mm b/renderer/native/ios/renderer/component/view/UIView+Hippy.mm index dfc58a9c1ad..48312d6bf6a 100644 --- a/renderer/native/ios/renderer/component/view/UIView+Hippy.mm +++ b/renderer/native/ios/renderer/component/view/UIView+Hippy.mm @@ -26,8 +26,51 @@ #import "UIView+MountEvent.h" #import "HippyLog.h" + +#define HippyEventMethod(name, value, type) \ +-(void)set##name : (type)value { \ +objc_setAssociatedObject(self, @selector(value), value, OBJC_ASSOCIATION_COPY_NONATOMIC); \ +} \ +-(type)value { \ +return objc_getAssociatedObject(self, _cmd); \ +} + + @implementation UIView (Hippy) + +#pragma mark - Event Related + +HippyEventMethod(OnClick, onClick, OnTouchEventHandler) +HippyEventMethod(OnPressIn, onPressIn, OnTouchEventHandler) +HippyEventMethod(OnPressOut, onPressOut, OnTouchEventHandler) +HippyEventMethod(OnLongClick, onLongClick, OnTouchEventHandler) +HippyEventMethod(OnTouchDown, onTouchDown, OnTouchEventHandler) +HippyEventMethod(OnTouchMove, onTouchMove, OnTouchEventHandler) +HippyEventMethod(OnTouchCancel, onTouchCancel, OnTouchEventHandler) +HippyEventMethod(OnTouchEnd, onTouchEnd, OnTouchEventHandler) +//HippyEventMethod(OnAttachedToWindow, onAttachedToWindow, HippyDirectEventBlock) +//HippyEventMethod(OnDetachedFromWindow, onDetachedFromWindow, HippyDirectEventBlock) + +- (BOOL)onInterceptTouchEvent { + return [objc_getAssociatedObject(self, _cmd) boolValue]; +} + +- (void)setOnInterceptTouchEvent:(BOOL)onInterceptTouchEvent { + objc_setAssociatedObject(self, @selector(onInterceptTouchEvent), @(onInterceptTouchEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (BOOL)onInterceptPullUpEvent { + return [objc_getAssociatedObject(self, _cmd) boolValue]; +} + +- (void)setOnInterceptPullUpEvent:(BOOL)onInterceptPullUpEvent { + objc_setAssociatedObject(self, @selector(onInterceptPullUpEvent), @(onInterceptPullUpEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + + +#pragma mark - + - (NSNumber *)hippyTag { return objc_getAssociatedObject(self, _cmd); } diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.h b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.h index 5a261431397..26d05498e5b 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.h +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.h @@ -25,7 +25,7 @@ #import "NativeRenderCollectionViewWaterfallLayout.h" #import "HippyComponent.h" #import "HippyScrollableProtocol.h" -#import "NativeRenderScrollProtocol.h" +#import "HippyScrollProtocol.h" #import "NativeRenderTouchesView.h" NS_ASSUME_NONNULL_BEGIN @@ -42,7 +42,7 @@ typedef NS_ENUM(NSInteger, NativeRenderScrollState) { * NativeRenderWaterfallView is a waterfall component, internal implementation is UICollectionView */ @interface NativeRenderWaterfallView : NativeRenderTouchesView { + NativeRenderCollectionViewDelegateWaterfallLayout, HippyScrollableProtocol, HippyScrollProtocol> { @protected NativeRenderWaterfallViewDataSource *_dataSource; NativeRenderWaterfallViewDataSource *_previousDataSource; diff --git a/renderer/native/ios/renderer/component/image/NativeRenderImageView+NativeRenderTouchesImplementation.h b/renderer/native/ios/renderer/touch_handler/HippyCustomTouchHandlerProtocol.h similarity index 53% rename from renderer/native/ios/renderer/component/image/NativeRenderImageView+NativeRenderTouchesImplementation.h rename to renderer/native/ios/renderer/touch_handler/HippyCustomTouchHandlerProtocol.h index ea89a85fcb3..f304125d65f 100644 --- a/renderer/native/ios/renderer/component/image/NativeRenderImageView+NativeRenderTouchesImplementation.h +++ b/renderer/native/ios/renderer/touch_handler/HippyCustomTouchHandlerProtocol.h @@ -20,15 +20,27 @@ * limitations under the License. */ -#import "NativeRenderImageView.h" -#import "NativeRenderTouchesProtocol.h" +#import "HippyBridgeModule.h" +#import -NS_ASSUME_NONNULL_BEGIN +/** + * used for custom touche handler + */ +@protocol HippyCustomTouchHandlerProtocol -/** This catagory is used to handle touches event for NativeRenderImageView +/** + * if The following methods return YES, HippyTouchHandler will return, + * see implements in HippyTouchHandler.m */ -@interface NativeRenderImageView (NativeRenderTouchesImplementation) +@optional +- (BOOL)customTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; -@end +- (BOOL)customTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; + +- (BOOL)customTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; -NS_ASSUME_NONNULL_END +- (BOOL)customTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; + +- (BOOL)customReset; + +@end diff --git a/renderer/native/ios/renderer/component/view/UIEvent+TouchResponder.h b/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.h similarity index 60% rename from renderer/native/ios/renderer/component/view/UIEvent+TouchResponder.h rename to renderer/native/ios/renderer/touch_handler/HippyTouchHandler.h index c869b7780ae..20f5006ef50 100644 --- a/renderer/native/ios/renderer/component/view/UIEvent+TouchResponder.h +++ b/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.h @@ -21,19 +21,19 @@ */ #import -#import "NativeRenderViewEventType.h" +#import "HippyBridge.h" -NS_ASSUME_NONNULL_BEGIN +/// Handles all gestures in Hippy +@interface HippyTouchHandler : UIGestureRecognizer -@interface UIEvent (TouchResponder) +/// Init method +/// - Parameters: +/// - view: rootView for touch, currently it can be HippyRootView or HippyModalHostView +/// - bridge: HippyBridge +- (instancetype)initWithRootView:(UIView *)view bridge:(HippyBridge *)bridge NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithTarget:(id)target action:(SEL)action NS_UNAVAILABLE; -- (void)setResponder:(__weak id)responder forType:(NativeRenderViewEventType)type; - -- (id)responderForType:(NativeRenderViewEventType)type; - -- (void)removeAllResponders; +/// Force cancel touch +- (void)cancelTouch; @end - -NS_ASSUME_NONNULL_END - diff --git a/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm b/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm new file mode 100644 index 00000000000..f8373a5bf2f --- /dev/null +++ b/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm @@ -0,0 +1,814 @@ +/*! + * iOS SDK + * + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed 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 "HippyTouchHandler.h" +#import "UIView+Hippy.h" +#import "HippyScrollProtocol.h" +#import "HippyUIManager.h" + +#include "dom/dom_listener.h" + +typedef void (^ViewBlock)(UIView *view, BOOL *stop); + +@interface UIView (HippyViewExtensions) +- (void)hippyLoopViewHierarchy:(ViewBlock)block; +- (void)hippyLoopSuperViewHierarchy:(ViewBlock)block; +- (UIView *)nextResponseViewAtPoint:(CGPoint)point; +@end + +@implementation UIView (HippyViewExtensions) + +- (void)hippyLoopViewHierarchy:(ViewBlock)block { + BOOL stop = NO; + if (block) { + block(self, &stop); + } + if (!stop) { + for (UIView *subview in self.subviews) { + [subview hippyLoopViewHierarchy:block]; + } + } +} + +- (void)hippyLoopSuperViewHierarchy:(ViewBlock)block { + BOOL stop = NO; + if (block) { + block(self, &stop); + } + if (!stop) { + [self.superview hippyLoopSuperViewHierarchy:block]; + } +} + +static bool isPointInsideView(UIView *view, CGPoint point) { + // use presentationLayer to adapt the view with animation + CALayer *presentationLayer = view.layer.presentationLayer; + if (presentationLayer) { + CGPoint layerPoint = [presentationLayer convertPoint:point fromLayer:view.layer]; + return [presentationLayer containsPoint:layerPoint]; + } + return false; +} + +- (UIView *)nextResponseViewAtPoint:(CGPoint)point { + UIView *superView = [self superview]; + if (superView && self.hippyTag) { + NSArray *subviews = [superView subviews]; + NSUInteger index = [subviews indexOfObject:self]; + if (0 != index) { + for (NSInteger i = index - 1; i >= 0; i--) { + UIView *siblingView = subviews[i]; + CGPoint pointInsiblingView = [self convertPoint:point toView:siblingView]; + BOOL pointInside = isPointInsideView(siblingView, pointInsiblingView); + if (pointInside) { + UIView *hitTestView = [siblingView hitTest:pointInsiblingView withEvent:nil]; + return hitTestView ? hitTestView : siblingView; + } + } + } + } + return superView; +} + +@end + + +@interface HippyTouchHandler () + +/** + * Indicate if event can be prevented in capturing process + * @param name event name in std::string type + * @return YES if event can be prevented in capturing process + */ +- (BOOL)canBePreventedByInCapturing:(const char *)name; + +/** + * Indicate if event can be prevented in bubbling process + * @param name event name in std::string type + * @return YES if event can be prevented in bubbling process + */ +- (BOOL)canBePreventInBubbling:(const char *)name; + +/** + * Indicate if event can capture + * @param name event name + * @return YES if event can capture + */ +- (BOOL)canCapture:(const char *)name; + +/** + * Indicate if event can bubble + * @param name event name + * @return YES if event can bubble + */ +- (BOOL)canBubble:(const char *)name; + +@end + + +@implementation HippyTouchHandler { + NSMutableArray *_moveTouches; + NSMutableArray *_moveViews; + + __weak UIView *_onPressInView; + __weak UIView *_onClickView; + __weak UIView *_onLongClickView; + + NSTimer *_toucheBeginTimer; + NSTimer *_touchLongTimer; + BOOL _bPressIn; + BOOL _bLongClick; + + __weak UIView *_rootView; + __weak UIView *_touchBeganView; + + CGPoint _startPoint; + HippyBridge *_bridge; + + NSHashTable *_onInterceptTouchEventView; + NSHashTable *_onInterceptPullUpEventView; +} + +- (instancetype)initWithRootView:(UIView *)view bridge:(HippyBridge *)bridge { + if (self = [super initWithTarget:nil action:NULL]) { + _moveTouches = [NSMutableArray new]; + _moveViews = [NSMutableArray new]; + _startPoint = CGPointZero; + _rootView = view; + self.delegate = self; + self.cancelsTouchesInView = NO; + _onInterceptTouchEventView = [NSHashTable weakObjectsHashTable]; + _onInterceptPullUpEventView = [NSHashTable weakObjectsHashTable]; + + _bridge = bridge; + } + return self; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesBegan:touches withEvent:event]; + if ([_bridge.customTouchHandler respondsToSelector:@selector(customTouchesBegan:withEvent:)]) { + BOOL shouldRecursive = [_bridge.customTouchHandler customTouchesBegan:touches withEvent:event]; + if (!shouldRecursive) { + return; + } + } + + UITouch *touch = [touches anyObject]; + _startPoint = [touch locationInView:touch.view]; + { + UIView *touchView = [touch view]; + CGPoint locationPoint = [touch locationInView:touchView]; + touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event]; + _touchBeganView = touchView; + NSDictionary *result = [self responseViewForAction:@[@"onPressIn", @"onTouchDown", @"onClick", @"onLongClick"] inView:touchView + atPoint:locationPoint]; + + UIView *view = result[@"onTouchDown"][@"view"]; + UIView *clickView = result[@"onClick"][@"view"]; + if (view) { + NSInteger index = [result[@"onTouchDown"][@"index"] integerValue]; + NSInteger clickIndex = NSNotFound; + if (clickView) { + clickIndex = [result[@"onClick"][@"index"] integerValue]; + } + + if (clickView == nil || (index <= clickIndex && clickIndex != NSNotFound)) { + CGPoint point = [touch locationInView:view]; + point = [view convertPoint:point toView:_rootView]; + if (view.onTouchDown) { + if ([self checkViewBelongToTouchHandler:view]) { +// view.onTouchDown(@{ @"page_x": @(point.x), @"page_y": @(point.y) }); + const char *name = hippy::kTouchStartEvent; + view.onTouchDown(point, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } + } + + if (result[@"onPressIn"][@"view"]) { + _onPressInView = result[@"onPressIn"][@"view"]; + [self clearTimer]; + _toucheBeginTimer = [NSTimer timerWithTimeInterval:0.1 target:self selector:@selector(scheduleTimer:) userInfo:nil repeats:NO]; + [[NSRunLoop mainRunLoop] addTimer:_toucheBeginTimer forMode:NSDefaultRunLoopMode]; + } + + _onClickView = clickView; + + if (result[@"onLongClick"][@"view"]) { + _onLongClickView = result[@"onLongClick"][@"view"]; + [self clearLongClickTimer]; + _touchLongTimer = [NSTimer timerWithTimeInterval:.6f target:self selector:@selector(longClickTimer:) userInfo:nil repeats:NO]; + [[NSRunLoop mainRunLoop] addTimer:_touchLongTimer forMode:NSDefaultRunLoopMode]; + } + } + + if (self.state == UIGestureRecognizerStatePossible) { + self.state = UIGestureRecognizerStateBegan; + } else if (self.state == UIGestureRecognizerStateBegan) { + self.state = UIGestureRecognizerStateChanged; + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesEnded:touches withEvent:event]; + if ([_bridge.customTouchHandler respondsToSelector:@selector(customTouchesEnded:withEvent:)]) { + BOOL shouldRecursive = [_bridge.customTouchHandler customTouchesEnded:touches withEvent:event]; + if (!shouldRecursive) { + return; + } + } + + UITouch *touch = [touches anyObject]; + { + UIView *touchView = [touch view]?:_touchBeganView; + CGPoint locationPoint = [touch locationInView:touchView]; + touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event]; + NSDictionary *result = [self responseViewForAction:@[@"onTouchEnd", @"onPressOut", @"onClick"] inView:touchView + atPoint:locationPoint]; + + UIView *view = result[@"onTouchEnd"][@"view"]; + UIView *clickView = result[@"onClick"][@"view"]; + if (view) { + NSInteger index = [result[@"onTouchEnd"][@"index"] integerValue]; + NSInteger clickIndex = NSNotFound; + if (clickView) { + clickIndex = [result[@"onClick"][@"index"] integerValue]; + } + + if (clickView == nil || (index <= clickIndex && clickIndex != NSNotFound)) { + CGPoint point = [touch locationInView:view]; + point = [view convertPoint:point toView:_rootView]; + if (view.onTouchEnd) { + if ([self checkViewBelongToTouchHandler:view]) { +// view.onTouchEnd(@{ @"page_x": @(point.x), @"page_y": @(point.y) }); + const char *name = hippy::kTouchEndEvent; + view.onTouchEnd(point, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } + } else { + if (_moveViews.count > 0 && _moveViews[0] && _moveViews[0][@"onTouchMove"]) { + NSDictionary *bundle = _moveViews[0][@"onTouchMove"]; + if (bundle && bundle[@"view"]) { + UIView *theView = bundle[@"view"]; + CGPoint point = [touch locationInView:theView]; + point = [theView convertPoint:point toView:_rootView]; + if (theView.onTouchEnd) { + if ([self checkViewBelongToTouchHandler:theView]) { +// theView.onTouchEnd(@{ @"page_x": @(point.x), @"page_y": @(point.y) }); + const char *name = hippy::kTouchEndEvent; + theView.onTouchEnd(point, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } + } + } + + if (result[@"onPressOut"][@"view"]) { + UIView *pressOutView = result[@"onPressOut"][@"view"]; + if (pressOutView == _onPressInView && pressOutView.onPressOut) { + if ([self checkViewBelongToTouchHandler:pressOutView]) { + const char *name = hippy::kPressOut; + pressOutView.onPressOut(CGPointZero, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + _onPressInView = nil; + _bPressIn = NO; + } + } + } + + if (clickView && clickView == _onClickView) { + if (!_bLongClick && clickView.onClick) { + if ([self checkViewBelongToTouchHandler:clickView]) { + const char *name = hippy::kClickEvent; + clickView.onClick(CGPointZero, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + [self clearTimer]; + [self clearLongClickTimer]; + _bPressIn = NO; + } + } + + self.state = UIGestureRecognizerStateEnded; + [_moveViews removeAllObjects]; + [_moveTouches removeAllObjects]; + [_onInterceptTouchEventView removeAllObjects]; + [_onInterceptPullUpEventView removeAllObjects]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesCancelled:touches withEvent:event]; + if ([_bridge.customTouchHandler respondsToSelector:@selector(customTouchesCancelled:withEvent:)]) { + BOOL shouldRecursive = [_bridge.customTouchHandler customTouchesCancelled:touches withEvent:event]; + if (!shouldRecursive) { + return; + } + } + + [_moveViews removeAllObjects]; + [_moveTouches removeAllObjects]; + + UITouch *touch = [touches anyObject]; + { + UIView *touchView = [touch view]?:_touchBeganView; + CGPoint locationPoint = [touch locationInView:touchView]; + touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event]; + NSDictionary *result = [self responseViewForAction:@[@"onTouchCancel", @"onPressOut", @"onClick"] inView:touchView + atPoint:locationPoint]; + UIView *clickView = result[@"onClick"][@"view"]; + UIView *view = result[@"onTouchCancel"][@"view"]; + if (view) { + NSInteger index = [result[@"onTouchCancel"][@"index"] integerValue]; + NSInteger clickIndex = NSNotFound; + if (clickView) { + clickIndex = [result[@"onClick"][@"index"] integerValue]; + } + + if (clickView == nil || (index <= clickIndex && clickIndex != NSNotFound)) { + CGPoint point = [touch locationInView:view]; + point = [view convertPoint:point toView:_rootView]; + if (view.onTouchCancel) { + if ([self checkViewBelongToTouchHandler:view]) { +// view.onTouchCancel(@{ @"page_x": @(point.x), @"page_y": @(point.y) }); + const char *name = hippy::kTouchCancelEvent; + view.onTouchCancel(point, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } + } + + if (result[@"onPressOut"][@"view"]) { + UIView *pressOutView = result[@"onPressOut"][@"view"]; + if (pressOutView == _onPressInView && pressOutView.onPressOut) { + if ([self checkViewBelongToTouchHandler:pressOutView]) { + const char *name = hippy::kPressOut; + pressOutView.onPressOut(CGPointZero, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } + } + self.state = UIGestureRecognizerStateCancelled; + self.enabled = NO; + self.enabled = YES; + [_onInterceptTouchEventView removeAllObjects]; + [_onInterceptPullUpEventView removeAllObjects]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesMoved:touches withEvent:event]; + if ([_bridge.customTouchHandler respondsToSelector:@selector(customTouchesMoved:withEvent:)]) { + BOOL shouldRecursive = [_bridge.customTouchHandler customTouchesMoved:touches withEvent:event]; + if (!shouldRecursive) { + return; + } + } + + UITouch *touch = [touches anyObject]; + CGPoint point = [touch locationInView:touch.view]; + + float dis = hypotf(_startPoint.x - point.x, _startPoint.y - point.y); + if (dis < 1.f) { + return; + } + [self clearTimer]; + _onClickView = nil; + + { + NSInteger index = [_moveTouches indexOfObject:touch]; + NSDictionary *result = nil; + if (index != NSNotFound) { + result = _moveViews[index]; + } else { + UIView *touchView = [touch view]?:_touchBeganView; + CGPoint locationPoint = [touch locationInView:touchView]; + touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event]; + NSDictionary *result = [self responseViewForAction:@[@"onTouchMove", @"onPressOut", @"onClick"] inView:touchView + atPoint:locationPoint]; + [_moveTouches addObject:touch]; + [_moveViews addObject:result]; + } + + if (_bPressIn) { + if (result[@"onLongClick"][@"view"]) { + _bLongClick = NO; + [self clearLongClickTimer]; + } + } + + UIView *clickView = result[@"onClick"][@"view"]; + UIView *view = result[@"onTouchMove"][@"view"]; + if (view) { + NSInteger index = [result[@"onTouchMove"][@"index"] integerValue]; + NSInteger clickIndex = NSNotFound; + if (clickView) { + clickIndex = [result[@"onClick"][@"index"] integerValue]; + } + + if (clickView == nil || (index <= clickIndex && clickIndex != NSNotFound)) { + if (view.onTouchMove) { + CGPoint point = [touch locationInView:view]; + point = [view convertPoint:point toView:_rootView]; + if ([self checkViewBelongToTouchHandler:view]) { +// view.onTouchMove(@{ @"page_x": @(point.x), @"page_y": @(point.y) }); + const char *name = hippy::kTouchMoveEvent; + view.onTouchMove(point, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } + } + } + self.state = UIGestureRecognizerStateChanged; +} + +- (BOOL)checkViewBelongToTouchHandler:(UIView *)view { + NSNumber *reactTag = [view hippyTag]; + UIView *checkView = [_bridge.uiManager viewForHippyTag:reactTag onRootTag:view.rootTag]; + if (!checkView) { + NSNumber *viewRootTag = [view rootTag]; + NSNumber *rootViewTag = [_rootView hippyTag]; + if (rootViewTag) { + return [viewRootTag isEqualToNumber:rootViewTag]; + } + } + return checkView == view; +} + +- (void)clearTimer { + if (_toucheBeginTimer) { + [_toucheBeginTimer invalidate]; + _toucheBeginTimer = nil; + } +} + +- (void)clearLongClickTimer { + if (_touchLongTimer) { + [_touchLongTimer invalidate]; + _touchLongTimer = nil; + } +} + +- (void)scheduleTimer:(__unused NSTimer *)timer { + if (!_bPressIn) { + if (_onPressInView && _onPressInView.onPressIn) { + if ([self checkViewBelongToTouchHandler:_onPressInView]) { + const char *name = hippy::kPressIn; + _onPressInView.onPressIn(CGPointZero, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + _bPressIn = YES; + } + + // self.state = UIGestureRecognizerStateEnded; +} + +- (void)longClickTimer:(__unused NSTimer *)timer { + if (!_bLongClick) { + _bLongClick = YES; + if (_onLongClickView && _onLongClickView.onLongClick) { + if ([self checkViewBelongToTouchHandler:_onLongClickView]) { +// _onLongClickView.onLongClick(@{}); + const char *name = hippy::kLongClickEvent; + _onLongClickView.onLongClick(CGPointZero, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } +} + +- (UIView *)rootView:(UIView *)view { + while (view.superview.hippyTag) { + view = view.superview; + } + return view; +} + +- (NSDictionary *)responseViewForAction:(NSArray *)actions inView:(UIView *)targetView atPoint:(CGPoint)point { + NSDictionary *result = [self nextResponseViewForAction:actions inView:targetView atPoint:point]; + NSNumber *innerTag = [targetView hippyTagAtPoint:point]; + if (innerTag && ![targetView.hippyTag isEqual:innerTag]) { + UIView *innerView = [_bridge.uiManager viewForHippyTag:innerTag onRootTag:targetView.rootTag]; + NSDictionary *innerResult = [self nextResponseViewForAction:actions inView:innerView atPoint:point]; + NSMutableDictionary *mergedResult = [result mutableCopy]; + [mergedResult addEntriesFromDictionary:innerResult]; + return mergedResult; + } + return result; +} + +- (NSDictionary *)nextResponseViewForAction:(NSArray *)actions inView:(UIView *)targetView atPoint:(CGPoint)point { + NSMutableDictionary *result = [NSMutableDictionary new]; + NSMutableArray *findActions = [NSMutableArray arrayWithArray:actions]; + UIView *view = (UIView *)targetView; + NSInteger index = 0; + while (view) { + BOOL onInterceptTouchEvent = view.onInterceptTouchEvent; + BOOL onInterceptPullUpEvent = view.onInterceptPullUpEvent; + if (onInterceptTouchEvent) { + findActions = [NSMutableArray arrayWithArray:actions]; + [result removeAllObjects]; + [_onInterceptTouchEventView addObject:view]; + } + + if (onInterceptPullUpEvent) { + if (point.y < _startPoint.y) { + findActions = [NSMutableArray arrayWithArray:actions]; + [result removeAllObjects]; + [_onInterceptPullUpEventView addObject:view]; + } + } + BOOL touchInterceptEvent = onInterceptTouchEvent || onInterceptPullUpEvent; + + if ((touchInterceptEvent && findActions.count == 0) || [view isKindOfClass:NSClassFromString(@"HippyRootContentView")]) { + break; + } else { + if ([findActions containsObject:@"onPressIn"] && view.onPressIn) { + if (!result[@"onClick"]) { + [result setValue:@{ @"view": view, @"index": @(index) } forKey:@"onPressIn"]; + } + [findActions removeObject:@"onPressIn"]; + } + + if ([findActions containsObject:@"onPressOut"] && view.onPressOut) { + [result setValue:@{ @"view": view, @"index": @(index) } forKey:@"onPressOut"]; + [findActions removeObject:@"onPressOut"]; + } + + if ([findActions containsObject:@"onClick"] && view.onClick) { + [result setValue:@{ @"view": view, @"index": @(index) } forKey:@"onClick"]; + [findActions removeObject:@"onClick"]; + } + + if ([findActions containsObject:@"onLongClick"] && view.onLongClick) { + [result setValue:@{ @"view": view, @"index": @(index) } forKey:@"onLongClick"]; + [findActions removeObject:@"onLongClick"]; + } + + if ([findActions containsObject:@"onTouchDown"] && view.onTouchDown) { + [result setValue:@{ @"view": view, @"index": @(index) } forKey:@"onTouchDown"]; + [findActions removeObject:@"onTouchDown"]; + } + + if ([findActions containsObject:@"onTouchMove"] && view.onTouchMove) { + [result setValue:@{ @"view": view, @"index": @(index) } forKey:@"onTouchMove"]; + [findActions removeObject:@"onTouchMove"]; + } + if ([findActions containsObject:@"onTouchCancel"] && view.onTouchCancel) { + [result setValue:@{ @"view": view, @"index": @(index) } forKey:@"onTouchCancel"]; + [findActions removeObject:@"onTouchCancel"]; + } + + if ([findActions containsObject:@"onTouchEnd"] && view.onTouchEnd) { + [result setValue:@{ @"view": view, @"index": @(index) } forKey:@"onTouchEnd"]; + [findActions removeObject:@"onTouchEnd"]; + } + + if (touchInterceptEvent) + break; + view = [view nextResponseViewAtPoint:point]; + index++; + } + } + return result; +} + +- (BOOL)gestureRecognizer:(__unused UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { + UIView *touchView = [touch view]; + while (touchView && ![touchView hippyTag]) { + NSArray *touchGestureRegs = [touchView gestureRecognizers]; + for (UIGestureRecognizer *touchGes in touchGestureRegs) { + if (![self canPreventGestureRecognizer:touchGes]) { + return NO; + } + } + touchView = [touchView superview]; + } + if ([self isYYTextView:touch.view]) { + return NO; + } + + if ([touch.view isKindOfClass:[UIButton class]]) { + return NO; + } + + __block BOOL ret = YES; + + [touch.view hippyLoopSuperViewHierarchy:^(UIView *view, BOOL *stop) { + if ([view conformsToProtocol:@protocol(HippyScrollProtocol)]) { + if ([(id)view isManualScrolling]) { + ret = NO; + *stop = YES; + } + } + }]; + + return ret; +} + +- (BOOL)isYYTextView:(UIView *)view { + Class yyTextViewClass = NSClassFromString(@"YYTextView"); + Class yyTextSelectionView = NSClassFromString(@"YYTextSelectionView"); + Class yyTextContainerView = NSClassFromString(@"YYTextContainerView"); + + if ([view isKindOfClass:yyTextViewClass] || [view isKindOfClass:yyTextSelectionView] || [view isKindOfClass:yyTextContainerView]) { + return YES; + } + + return NO; +} + +- (void)cancelTouch { + if (_onPressInView) { + _bPressIn = NO; + if (_onPressInView.onPressOut) { + if ([self checkViewBelongToTouchHandler:_onPressInView]) { + const char *name = hippy::kPressOut; + _onPressInView.onPressOut(CGPointZero, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } + _bLongClick = NO; + [self clearTimer]; + [self clearLongClickTimer]; + self.enabled = NO; + self.enabled = YES; +} + +- (void)reset { + if ([_bridge.customTouchHandler respondsToSelector:@selector(customReset)]) { + BOOL shouldRecursive = [_bridge.customTouchHandler customReset]; + if (!shouldRecursive) { + return; + } + } + + if (_onPressInView) { + _bPressIn = NO; + if (_onPressInView.onPressOut) { + if ([self checkViewBelongToTouchHandler:_onPressInView]) { + const char *name = hippy::kPressOut; + _onPressInView.onPressOut(CGPointZero, + [self canCapture:name], + [self canBubble:name], + [self canBePreventedByInCapturing:name], + [self canBePreventInBubbling:name]); + } + } + } + [self clearTimer]; + _bLongClick = NO; + [self clearLongClickTimer]; + [super reset]; +} + +- (BOOL)canPreventGestureRecognizer:(__unused UIGestureRecognizer *)preventedGestureRecognizer { + UIView *gestureView = [preventedGestureRecognizer view]; + for (UIView *view in _onInterceptTouchEventView) { + if ([gestureView isDescendantOfView:view] && gestureView != view && ![gestureView hippyTag]) { + return YES; + } + } + for (UIView *view in _onInterceptPullUpEventView) { + if ([gestureView isDescendantOfView:view] && gestureView != view && ![gestureView hippyTag]) { + return YES; + } + } + if ([preventedGestureRecognizer isKindOfClass:[self class]]) { + UIView *currentHandlerView = [self view]; + BOOL canPreventGestureRecognizer = [currentHandlerView isDescendantOfView:gestureView]; + return canPreventGestureRecognizer; + } + 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. + if (![preventingGestureRecognizer.view isDescendantOfView:_rootView]) { + return NO; + } + else if ([preventingGestureRecognizer isKindOfClass:[self class]]) { + UIView *currentHandlerView = [self view]; + UIView *gestureView = [preventingGestureRecognizer view]; + BOOL canPreventGestureRecognizer = [currentHandlerView isDescendantOfView:gestureView]; + return !canPreventGestureRecognizer; + } + else { + return ![preventingGestureRecognizer.view isDescendantOfView:self.view]; + } +} + +- (BOOL)gestureRecognizer:(__unused UIGestureRecognizer *)gestureRecognizer + shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + // Same condition for `failure of` as for `be prevented by`. + + return [self canBePreventedByGestureRecognizer:otherGestureRecognizer]; +} + + +#pragma mark - Event Helper Methods + +static BOOL IsGestureEvent(const char *name) { + if (!name) { + return NO; + } + if (0 == strcmp(name, hippy::kClickEvent) || + 0 == strcmp(name, hippy::kLongClickEvent) || + 0 == strcmp(name, hippy::kPressIn) || + 0 == strcmp(name, hippy::kPressOut) || + 0 == strcmp(name, hippy::kTouchStartEvent) || + 0 == strcmp(name, hippy::kTouchEndEvent) || + 0 == strcmp(name, hippy::kTouchMoveEvent) || + 0 == strcmp(name, hippy::kTouchCancelEvent)) { + return YES; + } + return NO; +} + +- (BOOL)canCapture:(const char *)name { + if (!name) { + return YES; + } + return IsGestureEvent(name); +} + +- (BOOL)canBubble:(const char *)name { + if (!name) { + return YES; + } + return IsGestureEvent(name); +} + +- (BOOL)canBePreventedByInCapturing:(const char *)name { + return NO; +} + +- (BOOL)canBePreventInBubbling:(const char *)name { + return NO; +} + + +@end +