diff --git a/SmartDeviceLink/SDLTouchManager.h b/SmartDeviceLink/SDLTouchManager.h index fb206665d..185aae174 100644 --- a/SmartDeviceLink/SDLTouchManager.h +++ b/SmartDeviceLink/SDLTouchManager.h @@ -40,6 +40,13 @@ typedef void(^SDLTouchEventHandler)(SDLTouch *touch, SDLTouchType type); */ @property (nonatomic, assign) CGFloat tapDistanceThreshold; +/** + Minimum distance for a pan gesture in the head unit's coordinate system, used for registering pan callbacks. + + @note Defaults to 8 px. + */ +@property (nonatomic, assign) CGFloat panDistanceThreshold; + /** * @abstract * Time (in seconds) between tap events to register a double-tap callback. diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 40b3b1c54..09189a18c 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -43,7 +43,13 @@ @interface SDLTouchManager () /*! * @abstract - * First Touch received from onOnTouchEvent. + * First touch received from onOnTouchEvent. + */ +@property (nonatomic, strong, nullable) SDLTouch *firstTouch; + +/*! + * @abstract + * Previous touch received from onOnTouchEvent. */ @property (nonatomic, strong, nullable) SDLTouch *previousTouch; @@ -106,6 +112,7 @@ - (instancetype)initWithHitTester:(nullable id)hitTes _movementTimeThreshold = 0.05f; _tapTimeThreshold = 0.4f; _tapDistanceThreshold = 50.0f; + _panDistanceThreshold = 8.0f; _touchEnabled = YES; _enableSyncedPanning = YES; @@ -208,6 +215,7 @@ - (void)sdl_handleTouchBegan:(SDLTouch *)touch { switch (touch.identifier) { case SDLTouchIdentifierFirstFinger: { + self.firstTouch = touch; self.previousTouch = touch; } break; case SDLTouchIdentifierSecondFinger: { @@ -235,6 +243,12 @@ - (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) { + return; + } switch (self.performingTouchType) { case SDLPerformingTouchTypeMultiTouch: { @@ -324,6 +338,7 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { case SDLPerformingTouchTypeNone: break; } + self.firstTouch = nil; self.previousTouch = nil; _performingTouchType = SDLPerformingTouchTypeNone; } @@ -363,6 +378,7 @@ - (void)sdl_handleTouchCanceled:(SDLTouch *)touch { case SDLPerformingTouchTypeNone: break; } + self.firstTouch = nil; self.previousTouch = nil; _performingTouchType = SDLPerformingTouchTypeNone; } diff --git a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m index 86204073c..0c45c687e 100644 --- a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m +++ b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m @@ -257,7 +257,54 @@ __block void (^performTouchEvent)(SDLTouchManager* touchManager, SDLOnTouchEvent expectedNumTimesHandlerCalled = 2; }); }); + + describe(@"when receiving a single tap with small movement", ^{ + + __block CGPoint movePoint; + __block SDLOnTouchEvent* firstOnTouchEventMove; + + beforeEach(^{ + const CGFloat moveDistance = touchManager.panDistanceThreshold; + movePoint = CGPointMake(controlPoint.x + moveDistance, controlPoint.y + moveDistance); + + SDLTouchCoord* firstTouchCoordMove = [[SDLTouchCoord alloc] init]; + firstTouchCoordMove.x = @(movePoint.x); + firstTouchCoordMove.y = @(movePoint.y); + + SDLTouchEvent* touchEventMove = [[SDLTouchEvent alloc] init]; + touchEventMove.touchEventId = @0; + touchEventMove.coord = [NSArray arrayWithObject:firstTouchCoordMove]; + touchEventMove.timeStamp = [NSArray arrayWithObject:@(firstTouchTimeStamp)]; + + firstOnTouchEventMove = [[SDLOnTouchEvent alloc] init]; + firstOnTouchEventMove.type = SDLTouchTypeMove; + firstOnTouchEventMove.event = [NSArray arrayWithObject:touchEventMove]; + + firstOnTouchEventEnd = [[SDLOnTouchEvent alloc] init]; + firstOnTouchEventEnd.type = SDLTouchTypeEnd; + firstOnTouchEventEnd.event = [NSArray arrayWithObject:touchEventMove]; + }); + + it(@"should correctly handle a single tap", ^{ + singleTapTests = ^(NSInvocation* invocation) { + __unsafe_unretained SDLTouchManager* touchManagerCallback; + CGPoint point; + [invocation getArgument:&touchManagerCallback atIndex:2]; + [invocation getArgument:&point atIndex:4]; + + expect(touchManagerCallback).to(equal(touchManager)); + expect(@(CGPointEqualToPoint(point, movePoint))).to(beTruthy()); + }; + + performTouchEvent(touchManager, firstOnTouchEventStart); + performTouchEvent(touchManager, firstOnTouchEventMove); + performTouchEvent(touchManager, firstOnTouchEventEnd); + expectedDidCallSingleTap = YES; + expectedNumTimesHandlerCalled = 3; + }); + }); + describe(@"when receiving a double tap", ^{ __block CGPoint averagePoint; __block SDLTouchEvent* secondTouchEvent;