-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Touch Manager Gesture Cancellation #727
Changes from 7 commits
edf6921
3077309
ddce172
917a9c5
e69d6be
960831b
4d34109
e8c6121
1d5aba9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,21 +32,21 @@ typedef NS_ENUM(NSUInteger, SDLPerformingTouchType) { | |
}; | ||
|
||
/*! | ||
* @abstract | ||
* @abstract | ||
* Touch Manager will ignore touches that represent more than 2 fingers on the screen. | ||
*/ | ||
static NSUInteger const MaximumNumberOfTouches = 2; | ||
|
||
@interface SDLTouchManager () | ||
|
||
/*! | ||
* @abstract | ||
* @abstract | ||
* First Touch received from onOnTouchEvent. | ||
*/ | ||
@property (nonatomic, strong, nullable) SDLTouch *previousTouch; | ||
|
||
/*! | ||
* @abstract | ||
* @abstract | ||
* Cached previous single tap used for double tap detection. | ||
*/ | ||
@property (nonatomic, strong, nullable) SDLTouch *singleTapTouch; | ||
|
@@ -91,7 +91,7 @@ - (instancetype)init { | |
_touchEnabled = YES; | ||
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_onTouchEvent:) name:SDLDidReceiveTouchEventNotification object:nil]; | ||
|
||
return self; | ||
} | ||
|
||
|
@@ -102,23 +102,28 @@ - (void)cancelPendingTouches { | |
|
||
#pragma mark - SDLDidReceiveTouchEventNotification | ||
|
||
/** | ||
* Handles detecting the type and state of the gesture and notifies the appropriate delegate callbacks. | ||
|
||
* @param notification A SDLOnTouchEvent notification. | ||
*/ | ||
- (void)sdl_onTouchEvent:(SDLRPCNotificationNotification *)notification { | ||
if (!self.isTouchEnabled | ||
|| (!self.touchEventHandler && !self.touchEventDelegate) | ||
|| ![notification.notification isKindOfClass:SDLOnTouchEvent.class]) { | ||
return; | ||
} | ||
|
||
SDLOnTouchEvent* onTouchEvent = (SDLOnTouchEvent*)notification.notification; | ||
|
||
SDLTouchType touchType = onTouchEvent.type; | ||
SDLTouchEvent *touchEvent = onTouchEvent.event.firstObject; | ||
SDLTouch *touch = [[SDLTouch alloc] initWithTouchEvent:touchEvent]; | ||
|
||
if (self.touchEventHandler) { | ||
self.touchEventHandler(touch, touchType); | ||
} | ||
|
||
if (!self.touchEventDelegate || (touch.identifier > MaximumNumberOfTouches)) { | ||
return; | ||
} | ||
|
@@ -129,10 +134,17 @@ - (void)sdl_onTouchEvent:(SDLRPCNotificationNotification *)notification { | |
[self sdl_handleTouchMoved:touch]; | ||
} else if ([onTouchEvent.type isEqualToEnum:SDLTouchTypeEnd]) { | ||
[self sdl_handleTouchEnded:touch]; | ||
} else if ([onTouchEvent.type isEqualToEnum:SDLTouchTypeCancel]) { | ||
[self sdl_handleTouchCanceled:touch]; | ||
} | ||
} | ||
|
||
#pragma mark - Private | ||
/** | ||
* Handles a BEGIN touch event sent by Core | ||
* | ||
* @param touch Gesture information | ||
*/ | ||
- (void)sdl_handleTouchBegan:(SDLTouch *)touch { | ||
if (!touch.isFirstFinger && !self.isTouchEnabled) { | ||
return; // no-op | ||
|
@@ -141,39 +153,42 @@ - (void)sdl_handleTouchBegan:(SDLTouch *)touch { | |
_performingTouchType = SDLPerformingTouchTypeSingleTouch; | ||
|
||
switch (touch.identifier) { | ||
case SDLTouchIdentifierFirstFinger: | ||
case SDLTouchIdentifierFirstFinger: | ||
self.previousTouch = touch; | ||
break; | ||
case SDLTouchIdentifierSecondFinger: | ||
|
||
case SDLTouchIdentifierSecondFinger: | ||
_performingTouchType = SDLPerformingTouchTypeMultiTouch; | ||
self.currentPinchGesture = [[SDLPinchGesture alloc] initWithFirstTouch:self.previousTouch | ||
secondTouch:touch]; | ||
self.currentPinchGesture = [[SDLPinchGesture alloc] initWithFirstTouch:self.previousTouch secondTouch:touch]; | ||
self.previousPinchDistance = self.currentPinchGesture.distance; | ||
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidStartAtCenterPoint:)]) { | ||
[self.touchEventDelegate touchManager:self | ||
pinchDidStartAtCenterPoint:self.currentPinchGesture.center]; | ||
[self.touchEventDelegate touchManager:self pinchDidStartAtCenterPoint:self.currentPinchGesture.center]; | ||
} | ||
break; | ||
} | ||
} | ||
|
||
/** | ||
* Handles a MOVE touch event sent by Core | ||
* | ||
* @param touch Gesture information | ||
*/ | ||
- (void)sdl_handleTouchMoved:(SDLTouch *)touch { | ||
if ((touch.timeStamp - self.previousTouch.timeStamp) <= (self.movementTimeThreshold * NSEC_PER_USEC) || !self.isTouchEnabled) { | ||
return; // no-op | ||
} | ||
|
||
switch (self.performingTouchType) { | ||
case SDLPerformingTouchTypeMultiTouch: | ||
case SDLPerformingTouchTypeMultiTouch: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also indented incorrectly: switch (self.performingTouchType) {
case SDLPerformingTouchTypeMultiTouch: {
switch (touch.identifier) {
case SDLTouchIdentifierFirstFinger: {
self.currentPinchGesture.firstTouch = touch;
} break;
case SDLTouchIdentifierSecondFinger: {
self.currentPinchGesture.secondTouch = touch;
} break;
}
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceivePinchAtCenterPoint:withScale:)]) {
CGFloat scale = self.currentPinchGesture.distance / self.previousPinchDistance;
[self.touchEventDelegate touchManager:self
didReceivePinchAtCenterPoint:self.currentPinchGesture.center
withScale:scale];
}
self.previousPinchDistance = self.currentPinchGesture.distance;
} break;
case SDLPerformingTouchTypeSingleTouch: {
_performingTouchType = SDLPerformingTouchTypePanningTouch;
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartAtPoint:)]) {
[self.touchEventDelegate touchManager:self
panningDidStartAtPoint:touch.location];
}
} break;
case SDLPerformingTouchTypePanningTouch: {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceivePanningFromPoint:toPoint:)]) {
[self.touchEventDelegate touchManager:self
didReceivePanningFromPoint:self.previousTouch.location
toPoint:touch.location];
}
} break;
case SDLPerformingTouchTypeNone: break;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
switch (touch.identifier) { | ||
case SDLTouchIdentifierFirstFinger: | ||
case SDLTouchIdentifierFirstFinger: | ||
self.currentPinchGesture.firstTouch = touch; | ||
break; | ||
case SDLTouchIdentifierSecondFinger: | ||
case SDLTouchIdentifierSecondFinger: | ||
self.currentPinchGesture.secondTouch = touch; | ||
break; | ||
} | ||
|
||
|
||
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceivePinchAtCenterPoint:withScale:)]) { | ||
CGFloat scale = self.currentPinchGesture.distance / self.previousPinchDistance; | ||
[self.touchEventDelegate touchManager:self | ||
|
@@ -183,43 +198,43 @@ - (void)sdl_handleTouchMoved:(SDLTouch *)touch { | |
|
||
self.previousPinchDistance = self.currentPinchGesture.distance; | ||
break; | ||
case SDLPerformingTouchTypeSingleTouch: | ||
|
||
case SDLPerformingTouchTypeSingleTouch: | ||
_performingTouchType = SDLPerformingTouchTypePanningTouch; | ||
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartAtPoint:)]) { | ||
[self.touchEventDelegate touchManager:self | ||
panningDidStartAtPoint:touch.location]; | ||
} | ||
break; | ||
case SDLPerformingTouchTypePanningTouch: | ||
|
||
case SDLPerformingTouchTypePanningTouch: | ||
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceivePanningFromPoint:toPoint:)]) { | ||
[self.touchEventDelegate touchManager:self | ||
didReceivePanningFromPoint:self.previousTouch.location | ||
toPoint:touch.location]; | ||
} | ||
break; | ||
case SDLPerformingTouchTypeNone: | ||
|
||
case SDLPerformingTouchTypeNone: | ||
break; | ||
} | ||
|
||
self.previousTouch = touch; | ||
} | ||
|
||
/** | ||
* Handles a END touch type notification sent by Core | ||
* | ||
* @param touch Gesture information | ||
*/ | ||
- (void)sdl_handleTouchEnded:(SDLTouch *)touch { | ||
if (!self.isTouchEnabled) { | ||
return; // no-op | ||
} | ||
|
||
switch (self.performingTouchType) { | ||
case SDLPerformingTouchTypeMultiTouch: | ||
switch (touch.identifier) { | ||
case SDLTouchIdentifierFirstFinger: | ||
self.currentPinchGesture.firstTouch = touch; | ||
break; | ||
case SDLTouchIdentifierSecondFinger: | ||
self.currentPinchGesture.secondTouch = touch; | ||
break; | ||
} | ||
|
||
case SDLPerformingTouchTypeMultiTouch: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also indented incorrectly: switch (self.performingTouchType) {
case SDLPerformingTouchTypeMultiTouch: {
[self sdl_setMultiTouchFingerTouchForTouch:touch];
if (self.currentPinchGesture.isValid) {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndAtCenterPoint:)]) {
[self.touchEventDelegate touchManager:self pinchDidEndAtCenterPoint:self.currentPinchGesture.center];
}
self.currentPinchGesture = nil;
}
} break;
case SDLPerformingTouchTypePanningTouch: {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndAtPoint:)]) {
[self.touchEventDelegate touchManager:self panningDidEndAtPoint:touch.location];
}
} break;
case SDLPerformingTouchTypeSingleTouch: {
if (self.singleTapTimer == nil) {
// Initial Tap
self.singleTapTouch = touch;
[self sdl_initializeSingleTapTimerAtPoint:self.singleTapTouch.location];
} else {
// Double Tap
[self sdl_cancelSingleTapTimer];
NSUInteger timeStampDelta = touch.timeStamp - self.singleTapTouch.timeStamp;
CGFloat xDelta = fabs(touch.location.x - self.singleTapTouch.location.x);
CGFloat yDelta = fabs(touch.location.y - self.singleTapTouch.location.y);
if (timeStampDelta <= self.tapTimeThreshold * NSEC_PER_USEC && xDelta <= self.tapDistanceThreshold && yDelta <= self.tapDistanceThreshold) {
CGPoint centerPoint = CGPointCenterOfPoints(touch.location,
self.singleTapTouch.location);
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveDoubleTapAtPoint:)]) {
[self.touchEventDelegate touchManager:self
didReceiveDoubleTapAtPoint:centerPoint];
}
}
self.singleTapTouch = nil;
}
} break;
case SDLPerformingTouchTypeNone: break;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
[self sdl_setMultiTouchFingerTouchForTouch:touch]; | ||
if (self.currentPinchGesture.isValid) { | ||
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndAtCenterPoint:)]) { | ||
[self.touchEventDelegate touchManager:self | ||
|
@@ -228,17 +243,21 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { | |
self.currentPinchGesture = nil; | ||
} | ||
break; | ||
case SDLPerformingTouchTypePanningTouch: | ||
|
||
case SDLPerformingTouchTypePanningTouch: | ||
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndAtPoint:)]) { | ||
[self.touchEventDelegate touchManager:self | ||
panningDidEndAtPoint:touch.location]; | ||
} | ||
break; | ||
case SDLPerformingTouchTypeSingleTouch: | ||
if (self.singleTapTimer == nil) { // Initial Tap | ||
|
||
case SDLPerformingTouchTypeSingleTouch: | ||
if (self.singleTapTimer == nil) { | ||
// Initial Tap | ||
self.singleTapTouch = touch; | ||
[self sdl_initializeSingleTapTimerAtPoint:self.singleTapTouch.location]; | ||
} else { // Double Tap | ||
} else { | ||
// Double Tap | ||
[self sdl_cancelSingleTapTimer]; | ||
|
||
NSUInteger timeStampDelta = touch.timeStamp - self.singleTapTouch.timeStamp; | ||
|
@@ -257,16 +276,80 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { | |
self.singleTapTouch = nil; | ||
} | ||
break; | ||
case SDLPerformingTouchTypeNone: | ||
|
||
case SDLPerformingTouchTypeNone: | ||
break; | ||
} | ||
self.previousTouch = nil; | ||
_performingTouchType = SDLPerformingTouchTypeNone; | ||
} | ||
|
||
/** | ||
* Handles a CANCEL touch event sent by CORE. The CANCEL touch event is sent when a gesture is interrupted during a video stream. This can happen when a system dialog box appears on the screen, such as when the user is alerted about an incoming phone call. | ||
* | ||
* Pinch and pan gesture subscribers are notified if the gesture is canceled. Tap gestures are simply canceled without notification. | ||
* | ||
* @param touch Gesture information | ||
*/ | ||
- (void)sdl_handleTouchCanceled:(SDLTouch *)touch { | ||
if (!self.isTouchEnabled) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This already exists in the calling method and should not be repeated here or in the other sub-handlers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. Removed from all sub-handlers |
||
return; // no-op | ||
} | ||
|
||
if (self.singleTapTimer != nil) { | ||
// Cancel any ongoing single tap timer | ||
[self sdl_cancelSingleTapTimer]; | ||
self.singleTapTouch = nil; | ||
} | ||
|
||
switch (self.performingTouchType) { | ||
case SDLPerformingTouchTypeMultiTouch: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also indented incorrectly: switch (self.performingTouchType) {
case SDLPerformingTouchTypeMultiTouch: {
[self sdl_setMultiTouchFingerTouchForTouch:touch];
if (self.currentPinchGesture.isValid) {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchCanceledAtCenterPoint:)]) {
[self.touchEventDelegate touchManager:self pinchCanceledAtCenterPoint:self.currentPinchGesture.center];
}
self.currentPinchGesture = nil;
}
} break;
case SDLPerformingTouchTypePanningTouch: {
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningCanceledAtPoint:)]) {
[self.touchEventDelegate touchManager:self panningCanceledAtPoint:touch.location];
}
} break;
case SDLPerformingTouchTypeSingleTouch: // fallthrough
case SDLPerformingTouchTypeNone: break;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
[self sdl_setMultiTouchFingerTouchForTouch:touch]; | ||
if (self.currentPinchGesture.isValid) { | ||
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchCanceledAtCenterPoint:)]) { | ||
[self.touchEventDelegate touchManager:self | ||
pinchCanceledAtCenterPoint:self.currentPinchGesture.center]; | ||
} | ||
self.currentPinchGesture = nil; | ||
} | ||
break; | ||
|
||
case SDLPerformingTouchTypePanningTouch: | ||
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningCanceledAtPoint:)]) { | ||
[self.touchEventDelegate touchManager:self | ||
panningCanceledAtPoint:touch.location]; | ||
} | ||
break; | ||
|
||
case SDLPerformingTouchTypeNone: | ||
case SDLPerformingTouchTypeSingleTouch: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverse this and the above case, they should be in the same order as other sub-handlers (this is done in the code provided in the above comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
break; | ||
} | ||
|
||
self.previousTouch = nil; | ||
_performingTouchType = SDLPerformingTouchTypeNone; | ||
} | ||
|
||
- (void)sdl_setMultiTouchFingerTouchForTouch:(SDLTouch *)touch { | ||
switch (touch.identifier) { | ||
case SDLTouchIdentifierFirstFinger: | ||
self.currentPinchGesture.firstTouch = touch; | ||
break; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add braces as in above cases There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
case SDLTouchIdentifierSecondFinger: | ||
self.currentPinchGesture.secondTouch = touch; | ||
break; | ||
} | ||
} | ||
|
||
/** | ||
* Creates a timer used to detect the type of tap gesture (single or double tap) | ||
* | ||
* @param point Screen coordinates of the tap gesture | ||
*/ | ||
- (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { | ||
__weak typeof(self) weakSelf = self; | ||
self.singleTapTimer = dispatch_create_timer(self.tapTimeThreshold, NO, ^{ | ||
// If timer was not canceled by a second tap then only one tap detected | ||
typeof(weakSelf) strongSelf = weakSelf; | ||
strongSelf.singleTapTouch = nil; | ||
[strongSelf sdl_cancelSingleTapTimer]; | ||
|
@@ -277,6 +360,9 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { | |
}); | ||
} | ||
|
||
/** | ||
* Cancels a tap gesture timer | ||
*/ | ||
- (void)sdl_cancelSingleTapTimer { | ||
if (self.singleTapTimer == NULL) { | ||
return; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This indentation for the whole switch is incorrect, should look like:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed