Skip to content

Commit

Permalink
Merge pull request #727 from smartdevicelink/feature/issue_673_touch_…
Browse files Browse the repository at this point in the history
…manager_gesture_cancellation

Touch Manager Gesture Cancellation
  • Loading branch information
joeljfischer authored Sep 12, 2017
2 parents afda6cc + 1d5aba9 commit 10ce67a
Show file tree
Hide file tree
Showing 5 changed files with 704 additions and 342 deletions.
1 change: 1 addition & 0 deletions SmartDeviceLink-iOS.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ ss.public_header_files = [
'SmartDeviceLink/SDLPutFileResponse.h',
'SmartDeviceLink/SDLReadDID.h',
'SmartDeviceLink/SDLReadDIDResponse.h',
'SmartDeviceLink/SDLRectangle.h',
'SmartDeviceLink/SDLRegisterAppInterface.h',
'SmartDeviceLink/SDLRegisterAppInterfaceResponse.h',
'SmartDeviceLink/SDLRequestType.h',
Expand Down
1 change: 1 addition & 0 deletions SmartDeviceLink.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ ss.public_header_files = [
'SmartDeviceLink/SDLPutFile.h',
'SmartDeviceLink/SDLPutFileResponse.h',
'SmartDeviceLink/SDLReadDID.h',
'SmartDeviceLink/SDLRectangle.h',
'SmartDeviceLink/SDLReadDIDResponse.h',
'SmartDeviceLink/SDLRegisterAppInterface.h',
'SmartDeviceLink/SDLRegisterAppInterfaceResponse.h',
Expand Down
180 changes: 124 additions & 56 deletions SmartDeviceLink/SDLTouchManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -91,7 +91,7 @@ - (instancetype)init {
_touchEnabled = YES;

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_onTouchEvent:) name:SDLDidReceiveTouchEventNotification object:nil];

return self;
}

Expand All @@ -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;
}
Expand All @@ -129,51 +134,56 @@ - (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
}

_performingTouchType = SDLPerformingTouchTypeSingleTouch;

switch (touch.identifier) {
case SDLTouchIdentifierFirstFinger:
case SDLTouchIdentifierFirstFinger: {
self.previousTouch = touch;
break;
case SDLTouchIdentifierSecondFinger:
} break;
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;
} 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) {
if ((touch.timeStamp - self.previousTouch.timeStamp) <= (self.movementTimeThreshold * NSEC_PER_USEC)) {
return; // no-op
}

switch (self.performingTouchType) {
case SDLPerformingTouchTypeMultiTouch:
case SDLPerformingTouchTypeMultiTouch: {
switch (touch.identifier) {
case SDLTouchIdentifierFirstFinger:
case SDLTouchIdentifierFirstFinger: {
self.currentPinchGesture.firstTouch = touch;
break;
case SDLTouchIdentifierSecondFinger:
} break;
case SDLTouchIdentifierSecondFinger: {
self.currentPinchGesture.secondTouch = touch;
break;
} break;
}


if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceivePinchAtCenterPoint:withScale:)]) {
CGFloat scale = self.currentPinchGesture.distance / self.previousPinchDistance;
[self.touchEventDelegate touchManager:self
Expand All @@ -182,63 +192,57 @@ - (void)sdl_handleTouchMoved:(SDLTouch *)touch {
}

self.previousPinchDistance = self.currentPinchGesture.distance;
break;
case SDLPerformingTouchTypeSingleTouch:
} break;
case SDLPerformingTouchTypeSingleTouch: {
_performingTouchType = SDLPerformingTouchTypePanningTouch;
if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartAtPoint:)]) {
[self.touchEventDelegate touchManager:self
panningDidStartAtPoint:touch.location];
}
break;
case SDLPerformingTouchTypePanningTouch:
} 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;
} break;
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: {
[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:
} 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
} break;
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;
Expand All @@ -256,17 +260,78 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch {

self.singleTapTouch = nil;
}
break;
case SDLPerformingTouchTypeNone:
break;
} break;
case SDLPerformingTouchTypeNone: break;
}

self.previousTouch = nil;
_performingTouchType = SDLPerformingTouchTypeNone;
}

/**
* Handles a CANCEL touch event sent by CORE. A 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.singleTapTimer != nil) {
// Cancel any ongoing single tap timer
[self sdl_cancelSingleTapTimer];
self.singleTapTouch = nil;
}

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;
}

self.previousTouch = nil;
_performingTouchType = SDLPerformingTouchTypeNone;
}

/**
* Saves the pinch touch gesture to the correct finger
*
* @param touch Gesture information
*/
- (void)sdl_setMultiTouchFingerTouchForTouch:(SDLTouch *)touch {
switch (touch.identifier) {
case SDLTouchIdentifierFirstFinger: {
self.currentPinchGesture.firstTouch = touch;
} break;
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];
Expand All @@ -277,6 +342,9 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point {
});
}

/**
* Cancels a tap gesture timer
*/
- (void)sdl_cancelSingleTapTimer {
if (self.singleTapTimer == NULL) {
return;
Expand Down
20 changes: 20 additions & 0 deletions SmartDeviceLink/SDLTouchManagerDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)touchManager:(SDLTouchManager *)manager panningDidEndAtPoint:(CGPoint)point;

/**
* @abstract
* Panning canceled.
* @param manager
* Current initalized SDLTouchManager issuing the callback.
* @param point
* Location of the panning's end point in the head unit's coordinate system.
*/
- (void)touchManager:(SDLTouchManager *)manager panningCanceledAtPoint:(CGPoint)point;

/**
* @abstract
* Pinch did start.
Expand Down Expand Up @@ -101,6 +111,16 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)touchManager:(SDLTouchManager *)manager pinchDidEndAtCenterPoint:(CGPoint)point;

/**
* @abstract
* Pinch canceled.
* @param manager
* Current initalized SDLTouchManager issuing the callback.
* @param point
* Center point of the pinch in the head unit's coordinate system.
*/
- (void)touchManager:(SDLTouchManager *)manager pinchCanceledAtCenterPoint:(CGPoint)point;

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 10ce67a

Please sign in to comment.