Skip to content

Commit

Permalink
Merge pull request #1209 from smartdevicelink/bugfix/issue_1207_hapti…
Browse files Browse the repository at this point in the history
…c_input_checked_on_wrong_thread

Hit test for a single tap now checked on main thread
  • Loading branch information
joeljfischer authored Apr 3, 2019
2 parents c7fbc61 + b067fc9 commit e222a5b
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 92 deletions.
8 changes: 4 additions & 4 deletions SmartDeviceLink/SDLFocusableItemLocator.m
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ - (void)sdl_sendHapticRPC {
}

NSMutableArray<SDLHapticRect *> *hapticRects = [[NSMutableArray alloc] init];

for (UIView *view in self.focusableViews) {
CGPoint originOnScreen = [self.viewController.view convertPoint:view.frame.origin toView:nil];
CGRect convertedRect = {originOnScreen, view.bounds.size};
Expand All @@ -115,15 +115,15 @@ - (void)sdl_sendHapticRPC {
SDLHapticRect *hapticRect = [[SDLHapticRect alloc] initWithId:(UInt32)rectId rect:rect];
[hapticRects addObject:hapticRect];
}

SDLSendHapticData* hapticRPC = [[SDLSendHapticData alloc] initWithHapticRectData:hapticRects];
[self.connectionManager sendConnectionManagerRequest:hapticRPC withResponseHandler:nil];
}

#pragma mark SDLFocusableItemHitTester functions
- (nullable UIView *)viewForPoint:(CGPoint)point {
UIView *selectedView = nil;

for (UIView *view in self.focusableViews) {
//Convert the absolute location to local location and check if that falls within view boundary
CGPoint localPoint = [view convertPoint:point fromView:self.viewController.view];
Expand All @@ -137,7 +137,7 @@ - (nullable UIView *)viewForPoint:(CGPoint)point {
}
}
}

return selectedView;
}

Expand Down
52 changes: 38 additions & 14 deletions SmartDeviceLink/SDLTouchManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ - (void)sdl_handleTouchBegan:(SDLTouch *)touch {
self.currentPinchGesture = [[SDLPinchGesture alloc] initWithFirstTouch:self.previousTouch secondTouch:touch];
self.previousPinchDistance = self.currentPinchGesture.distance;
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidStartInView:atCenterPoint:)]) {
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil;
[self.touchEventDelegate touchManager:self pinchDidStartInView:hitView atCenterPoint:self.currentPinchGesture.center];
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil;
[self.touchEventDelegate touchManager:self pinchDidStartInView:hitView atCenterPoint:self.currentPinchGesture.center];
}
} break;
}
Expand All @@ -247,7 +247,7 @@ - (void)sdl_handleTouchMoved:(SDLTouch *)touch {
return; // no-op
}
#pragma clang diagnostic pop

CGFloat xDelta = fabs(touch.location.x - self.firstTouch.location.x);
CGFloat yDelta = fabs(touch.location.y - self.firstTouch.location.y);
if (xDelta <= self.panDistanceThreshold && yDelta <= self.panDistanceThreshold) {
Expand Down Expand Up @@ -275,8 +275,8 @@ - (void)sdl_handleTouchMoved:(SDLTouch *)touch {

_performingTouchType = SDLPerformingTouchTypePanningTouch;
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartInView:atPoint:)]) {
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil;
[self.touchEventDelegate touchManager:self panningDidStartInView:hitView atPoint:touch.location];
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil;
[self.touchEventDelegate touchManager:self panningDidStartInView:hitView atPoint:touch.location];
}
} break;
case SDLPerformingTouchTypePanningTouch: {
Expand All @@ -302,18 +302,18 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch {
[self sdl_setMultiTouchFingerTouchForTouch:touch];
if (self.currentPinchGesture.isValid) {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndInView:atCenterPoint:)]) {
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil;
[self.touchEventDelegate touchManager:self pinchDidEndInView:hitView atCenterPoint:self.currentPinchGesture.center];
self.currentPinchGesture = nil;
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil;
[self.touchEventDelegate touchManager:self pinchDidEndInView:hitView atCenterPoint:self.currentPinchGesture.center];
self.currentPinchGesture = nil;
} else {
self.currentPinchGesture = nil;
}
}
} break;
case SDLPerformingTouchTypePanningTouch: {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndInView:atPoint:)]) {
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil;
[self.touchEventDelegate touchManager:self panningDidEndInView:hitView atPoint:touch.location];
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil;
[self.touchEventDelegate touchManager:self panningDidEndInView:hitView atPoint:touch.location];
}
} break;
case SDLPerformingTouchTypeSingleTouch: {
Expand All @@ -333,8 +333,8 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch {
CGPoint centerPoint = CGPointCenterOfPoints(touch.location,
self.singleTapTouch.location);
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveDoubleTapForView:atPoint:)]) {
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:centerPoint] : nil;
[self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:hitView atPoint:centerPoint];
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:centerPoint] : nil;
[self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:hitView atPoint:centerPoint];
}
}

Expand Down Expand Up @@ -420,12 +420,36 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point {
strongSelf.singleTapTouch = nil;
[strongSelf sdl_cancelSingleTapTimer];
if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapForView:atPoint:)]) {
UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:point] : nil;
[strongSelf.touchEventDelegate touchManager:strongSelf didReceiveSingleTapForView:hitView atPoint:point];
[self sdl_getSingleTapHitView:point hitViewHandler:^(UIView * _Nullable selectedView) {
[strongSelf.touchEventDelegate touchManager:strongSelf didReceiveSingleTapForView:selectedView atPoint:point];
}];
}
});
}

/**
* HAX to preserve the thread on which the delegate is notified for single taps; returning on the main thread would be a breaking change. All other touch gestures currently notify the delegate on the main thread. The single tap timer runs on a background thread so when a single tap is detected the hit test needs to be done on the main thread and then the result is returned on a background thread.
*
* Checks if a single tap is inside a view. As the single tap timer is run on a background thread, the check is done on a main thread and then the result is returned on a background thread.
*
* @param point Screen coordinates of the tap gesture
* @param hitViewHandler A handler that returns the view the point is inside of; nil if the point does not lie inside of a view
*/
- (void)sdl_getSingleTapHitView:(CGPoint)point hitViewHandler:(nullable void (^)(UIView * __nullable hitView))hitViewHandler {
if (!self.hitTester) {
if (!hitViewHandler) { return; }
return hitViewHandler(nil);
}

dispatch_async(dispatch_get_main_queue(), ^{
UIView *hitView = [self.hitTester viewForPoint:point];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (!hitViewHandler) { return; }
return hitViewHandler(hitView);
});
});
}

/**
* Cancels a tap gesture timer
*/
Expand Down
Loading

0 comments on commit e222a5b

Please sign in to comment.