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
…#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
  • Loading branch information
wwwcg authored Jun 11, 2024
1 parent a6d3da3 commit 7ab2ec1
Showing 1 changed file with 28 additions and 9 deletions.
37 changes: 28 additions & 9 deletions renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ @implementation HippyTouchHandler {
BOOL _bLongClick;

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

CGPoint _startPoint;
HippyBridge *_bridge;
Expand All @@ -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];
Expand All @@ -179,7 +180,9 @@ - (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;
if (touchView) {
[_touchBeganViews addObject:touchView];
}
NSDictionary *result = [self responseViewForAction:@[@"onPressIn", @"onTouchDown", @"onClick", @"onLongClick"] inView:touchView
atPoint:locationPoint];

Expand Down Expand Up @@ -243,8 +246,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 @@ -332,6 +337,7 @@ - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateEnded;
[_moveViews removeAllObjects];
[_moveTouches removeAllObjects];
[_touchBeganViews removeAllObjects];
[_onInterceptTouchEventView removeAllObjects];
[_onInterceptPullUpEventView removeAllObjects];
}
Expand All @@ -349,8 +355,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 @@ -398,6 +406,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 @@ -427,7 +436,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 @@ -513,8 +526,6 @@ - (void)scheduleTimer:(__unused NSTimer *)timer {
}
_bPressIn = YES;
}

// self.state = UIGestureRecognizerStateEnded;
}

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

0 comments on commit 7ab2ec1

Please sign in to comment.