Skip to content

Commit

Permalink
fix(ios): loss of touch end or cancel event in multi-finger scenarios
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wwwcg committed May 14, 2024
1 parent 48573ef commit 6e0903d
Showing 1 changed file with 26 additions and 9 deletions.
35 changes: 26 additions & 9 deletions ios/sdk/base/HippyTouchHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ @implementation HippyTouchHandler {
BOOL _bLongClick;

__weak UIView *_rootView;
__weak UIView *_touchBeganView;
NSMutableArray<UIView *> *_touchBeganViews;

CGPoint _startPoint;
HippyBridge *_bridge;
Expand All @@ -86,6 +86,7 @@ - (instancetype)initWithRootView:(UIView *)view {
_moveViews = [NSMutableArray new];
_startPoint = CGPointZero;
_rootView = view;
_touchBeganViews = [NSMutableArray new];
self.delegate = self;
self.cancelsTouchesInView = NO;
_onInterceptTouchEventView = [NSHashTable weakObjectsHashTable];
Expand Down Expand Up @@ -116,7 +117,7 @@ - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UIView *touchView = [touch view];
CGPoint locationPoint = [touch locationInView:touchView];
touchView = touchView?:[self.view.window hitTest:locationPoint withEvent:event];
_touchBeganView = touchView;
[_touchBeganViews addObject:touchView];
NSDictionary *result = [self responseViewForAction:@[@"onPressIn", @"onTouchDown", @"onClick", @"onLongClick"] inView:touchView
atPoint:locationPoint];

Expand Down Expand Up @@ -174,8 +175,10 @@ - (void)touchesEnded:(NSSet<UITouch *> *)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
Expand Down Expand Up @@ -241,6 +244,7 @@ - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateEnded;
[_moveViews removeAllObjects];
[_moveTouches removeAllObjects];
[_touchBeganViews removeAllObjects];
[_onInterceptTouchEventView removeAllObjects];
[_onInterceptPullUpEventView removeAllObjects];
}
Expand All @@ -258,8 +262,10 @@ - (void)touchesCancelled:(NSSet<UITouch *> *)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
Expand Down Expand Up @@ -296,6 +302,7 @@ - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
self.state = UIGestureRecognizerStateCancelled;
self.enabled = NO;
self.enabled = YES;
[_touchBeganViews removeAllObjects];
[_onInterceptTouchEventView removeAllObjects];
[_onInterceptPullUpEventView removeAllObjects];
}
Expand Down Expand Up @@ -325,7 +332,11 @@ - (void)touchesMoved:(NSSet<UITouch *> *)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
Expand Down Expand Up @@ -400,8 +411,6 @@ - (void)scheduleTimer:(__unused NSTimer *)timer {
}
_bPressIn = YES;
}

// self.state = UIGestureRecognizerStateEnded;
}

- (void)longClickTimer:(__unused NSTimer *)timer {
Expand Down Expand Up @@ -590,6 +599,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];
Expand Down

0 comments on commit 6e0903d

Please sign in to comment.