From 7ab2ec170ff1d5469cd36abde41f4ba728727cc6 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 11 Jun 2024 17:42:47 +0800 Subject: [PATCH] fix(ios): loss of touch end or cancel event in multi-finger scenarios (#3892) * fix(ios): loss of touch end or cancel event in multi-finger scenarios In a multi-finger scenario, the _touchBeganView local variable only records the last touch view since touch began is entered multiple times, causing the began and cancel/end events to be unmatched. To fix this, we use a simple approach - that record all touch began Views and send events to all recorded views at the end. Please note that we have not fully adapted the multi-fingered scenario. * fix(ios): resolve exception where touchView may be nil --- .../touch_handler/HippyTouchHandler.mm | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm b/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm index f8373a5bf2f..3bd8dd5c905 100644 --- a/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm +++ b/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm @@ -139,7 +139,7 @@ @implementation HippyTouchHandler { BOOL _bLongClick; __weak UIView *_rootView; - __weak UIView *_touchBeganView; + NSMutableArray *_touchBeganViews; CGPoint _startPoint; HippyBridge *_bridge; @@ -154,6 +154,7 @@ - (instancetype)initWithRootView:(UIView *)view bridge:(HippyBridge *)bridge { _moveViews = [NSMutableArray new]; _startPoint = CGPointZero; _rootView = view; + _touchBeganViews = [NSMutableArray new]; self.delegate = self; self.cancelsTouchesInView = NO; _onInterceptTouchEventView = [NSHashTable weakObjectsHashTable]; @@ -179,7 +180,9 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UIView *touchView = [touch view]; CGPoint locationPoint = [touch locationInView:touchView]; touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event]; - _touchBeganView = touchView; + if (touchView) { + [_touchBeganViews addObject:touchView]; + } NSDictionary *result = [self responseViewForAction:@[@"onPressIn", @"onTouchDown", @"onClick", @"onLongClick"] inView:touchView atPoint:locationPoint]; @@ -243,8 +246,10 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { } UITouch *touch = [touches anyObject]; - { - UIView *touchView = [touch view]?:_touchBeganView; + for (UIView *beganView in _touchBeganViews) { + // The touch processing logic here does not apply to multi-fingered scenarios, + // and needs to be further improved in the future. + UIView *touchView = beganView; CGPoint locationPoint = [touch locationInView:touchView]; touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event]; NSDictionary *result = [self responseViewForAction:@[@"onTouchEnd", @"onPressOut", @"onClick"] inView:touchView @@ -332,6 +337,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { self.state = UIGestureRecognizerStateEnded; [_moveViews removeAllObjects]; [_moveTouches removeAllObjects]; + [_touchBeganViews removeAllObjects]; [_onInterceptTouchEventView removeAllObjects]; [_onInterceptPullUpEventView removeAllObjects]; } @@ -349,8 +355,10 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event [_moveTouches removeAllObjects]; UITouch *touch = [touches anyObject]; - { - UIView *touchView = [touch view]?:_touchBeganView; + for (UIView *beganView in _touchBeganViews) { + // The touch processing logic here does not apply to multi-fingered scenarios, + // and needs to be further improved in the future. + UIView *touchView = beganView; CGPoint locationPoint = [touch locationInView:touchView]; touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event]; NSDictionary *result = [self responseViewForAction:@[@"onTouchCancel", @"onPressOut", @"onClick"] inView:touchView @@ -398,6 +406,7 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event self.state = UIGestureRecognizerStateCancelled; self.enabled = NO; self.enabled = YES; + [_touchBeganViews removeAllObjects]; [_onInterceptTouchEventView removeAllObjects]; [_onInterceptPullUpEventView removeAllObjects]; } @@ -427,7 +436,11 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (index != NSNotFound) { result = _moveViews[index]; } else { - UIView *touchView = [touch view]?:_touchBeganView; + // The touch processing logic here does not apply to multi-fingered scenarios, + // and needs to be further improved in the future. + // To keep things simple and to be compatible with the historical logic, + // we only use the first view clicked as a touchView + UIView *touchView = [touch view] ?: _touchBeganViews.firstObject; CGPoint locationPoint = [touch locationInView:touchView]; touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event]; NSDictionary *result = [self responseViewForAction:@[@"onTouchMove", @"onPressOut", @"onClick"] inView:touchView @@ -513,8 +526,6 @@ - (void)scheduleTimer:(__unused NSTimer *)timer { } _bPressIn = YES; } - - // self.state = UIGestureRecognizerStateEnded; } - (void)longClickTimer:(__unused NSTimer *)timer { @@ -717,6 +728,14 @@ - (void)reset { } } } + + // Final cleanup to prevent abnormal situations where the touch began/end/cancel mismatch + if (_touchBeganViews.count != 0) { + [_touchBeganViews removeAllObjects]; + [_moveViews removeAllObjects]; + [_moveTouches removeAllObjects]; + } + [self clearTimer]; _bLongClick = NO; [self clearLongClickTimer];