From 63396f3a12db3c98bad215e6e17352ea8abe2414 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Thu, 28 Mar 2019 09:45:53 -0400 Subject: [PATCH 01/11] Checking coordinates is now done on main thread --- SmartDeviceLink/SDLTouchManager.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 518877e96..8b80d6aeb 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -227,8 +227,10 @@ - (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:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil; [self.touchEventDelegate touchManager:self pinchDidStartInView:hitView atCenterPoint:self.currentPinchGesture.center]; + }); } } break; } @@ -275,8 +277,10 @@ - (void)sdl_handleTouchMoved:(SDLTouch *)touch { _performingTouchType = SDLPerformingTouchTypePanningTouch; if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartInView:atPoint:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil; [self.touchEventDelegate touchManager:self panningDidStartInView:hitView atPoint:touch.location]; + }); } } break; case SDLPerformingTouchTypePanningTouch: { @@ -302,9 +306,11 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { [self sdl_setMultiTouchFingerTouchForTouch:touch]; if (self.currentPinchGesture.isValid) { if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndInView:atCenterPoint:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ 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; } @@ -312,8 +318,10 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { } break; case SDLPerformingTouchTypePanningTouch: { if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndInView:atPoint:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil; [self.touchEventDelegate touchManager:self panningDidEndInView:hitView atPoint:touch.location]; + }); } } break; case SDLPerformingTouchTypeSingleTouch: { @@ -333,8 +341,10 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { CGPoint centerPoint = CGPointCenterOfPoints(touch.location, self.singleTapTouch.location); if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveDoubleTapForView:atPoint:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:centerPoint] : nil; [self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:hitView atPoint:centerPoint]; + }); } } @@ -420,8 +430,10 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { strongSelf.singleTapTouch = nil; [strongSelf sdl_cancelSingleTapTimer]; if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapForView:atPoint:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:point] : nil; [strongSelf.touchEventDelegate touchManager:strongSelf didReceiveSingleTapForView:hitView atPoint:point]; + }); } }); } From e322fffb639e981458ec5d0be0ff4f2944bb745a Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Thu, 28 Mar 2019 13:35:12 -0400 Subject: [PATCH 02/11] Fixed failing `SDLTouchManager` test cases --- .../UtilitiesSpecs/Touches/SDLTouchManagerSpec.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m index 25fad6316..1981cc804 100644 --- a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m +++ b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m @@ -94,6 +94,9 @@ @interface SDLTouchManager () __block void (^performTouchEvent)(SDLTouchManager* touchManager, SDLOnTouchEvent* onTouchEvent) = ^(SDLTouchManager* touchManager, SDLOnTouchEvent* onTouchEvent) { SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveTouchEventNotification object:nil rpcNotification:onTouchEvent]; [[NSNotificationCenter defaultCenter] postNotification:notification]; + + // Since notifications are sent to te SDLTouchManagerDelegate observers on the main thread, force the block to execute manually on the main thread. If this is not done, the test cases may fail. + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; }; beforeEach(^{ From 1bb7e47bd8745f5c4d52548eed99c6824a151e83 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Mon, 1 Apr 2019 14:25:10 -0400 Subject: [PATCH 03/11] Check for hit view is now done in `SDLFocusableItemLocator` --- SmartDeviceLink/SDLFocusableItemHitTester.h | 4 +- SmartDeviceLink/SDLFocusableItemLocator.m | 34 +++---- SmartDeviceLink/SDLTouchManager.m | 95 ++++++++++++++----- .../ProxySpecs/SDLHapticManagerSpec.m | 21 ++-- .../Touches/SDLTouchManagerSpec.m | 3 - 5 files changed, 103 insertions(+), 54 deletions(-) diff --git a/SmartDeviceLink/SDLFocusableItemHitTester.h b/SmartDeviceLink/SDLFocusableItemHitTester.h index 7dd7662b4..8a94f3714 100644 --- a/SmartDeviceLink/SDLFocusableItemHitTester.h +++ b/SmartDeviceLink/SDLFocusableItemHitTester.h @@ -11,6 +11,8 @@ NS_ASSUME_NONNULL_BEGIN +typedef void (^SDLFocusableItemHitTesterSelectedViewHandler)(UIView * __nullable selectedView); + @protocol SDLFocusableItemHitTester /** @@ -19,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @param point Point to check for a view @return point UIView object or nil */ -- (nullable UIView *)viewForPoint:(CGPoint)point; +- (void)viewForPoint:(CGPoint)point selectedViewHandler:(nullable SDLFocusableItemHitTesterSelectedViewHandler)selectedViewHandler; @end diff --git a/SmartDeviceLink/SDLFocusableItemLocator.m b/SmartDeviceLink/SDLFocusableItemLocator.m index 1969512e5..79838f089 100644 --- a/SmartDeviceLink/SDLFocusableItemLocator.m +++ b/SmartDeviceLink/SDLFocusableItemLocator.m @@ -121,24 +121,26 @@ - (void)sdl_sendHapticRPC { } #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]; - if ([view pointInside:localPoint withEvent:nil]) { - if (selectedView != nil) { - selectedView = nil; - break; - //the point has been indentified in two views. We cannot identify which with confidence. - } else { - selectedView = view; +- (void)viewForPoint:(CGPoint)point selectedViewHandler:(nullable void (^)(UIView * _Nullable))selectedViewHandler { + dispatch_async(dispatch_get_main_queue(), ^{ + 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]; + if ([view pointInside:localPoint withEvent:nil]) { + if (selectedView != nil) { + selectedView = nil; + break; + //the point has been indentified in two views. We cannot identify which with confidence. + } else { + selectedView = view; + } } } - } - - return selectedView; + + return selectedViewHandler(selectedView); + }); } #pragma mark notifications diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 8b80d6aeb..35a1f0cf0 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -227,15 +227,19 @@ - (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:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil; - [self.touchEventDelegate touchManager:self pinchDidStartInView:hitView atCenterPoint:self.currentPinchGesture.center]; - }); + if (self.hitTester) { + [self.hitTester viewForPoint:self.currentPinchGesture.center selectedViewHandler:^(UIView * _Nullable selectedView) { + [self sdl_notifyDelegatePinchBeganAtCenterPoint:self.currentPinchGesture.center view:selectedView]; + }]; + } else { + [self sdl_notifyDelegatePinchBeganAtCenterPoint:self.currentPinchGesture.center view:nil]; + } } } break; } } + /** * Handles a MOVE touch event sent by Core * @@ -249,7 +253,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) { @@ -277,10 +281,13 @@ - (void)sdl_handleTouchMoved:(SDLTouch *)touch { _performingTouchType = SDLPerformingTouchTypePanningTouch; if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartInView:atPoint:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil; - [self.touchEventDelegate touchManager:self panningDidStartInView:hitView atPoint:touch.location]; - }); + if (self.hitTester) { + [self.hitTester viewForPoint:touch.location selectedViewHandler:^(UIView * _Nullable selectedView) { + [self sdl_notifyDelegatePanningDidStartInView:selectedView point:touch.location]; + }]; + } else { + [self sdl_notifyDelegatePanningDidStartInView:nil point:touch.location]; + } } } break; case SDLPerformingTouchTypePanningTouch: { @@ -306,11 +313,15 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { [self sdl_setMultiTouchFingerTouchForTouch:touch]; if (self.currentPinchGesture.isValid) { if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndInView:atCenterPoint:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil; - [self.touchEventDelegate touchManager:self pinchDidEndInView:hitView atCenterPoint:self.currentPinchGesture.center]; + if (self.hitTester) { + [self.hitTester viewForPoint:self.currentPinchGesture.center selectedViewHandler:^(UIView * _Nullable selectedView) { + [self sdl_notifyDelegatePinchDidEndInView:selectedView centerPoint:self.currentPinchGesture.center]; + self.currentPinchGesture = nil; + }]; + } else { + [self sdl_notifyDelegatePinchDidEndInView:nil centerPoint:self.currentPinchGesture.center]; self.currentPinchGesture = nil; - }); + } } else { self.currentPinchGesture = nil; } @@ -318,10 +329,13 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { } break; case SDLPerformingTouchTypePanningTouch: { if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndInView:atPoint:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:touch.location] : nil; - [self.touchEventDelegate touchManager:self panningDidEndInView:hitView atPoint:touch.location]; - }); + if (self.hitTester) { + [self.hitTester viewForPoint:touch.location selectedViewHandler:^(UIView * _Nullable selectedView) { + [self sdl_notifyDelegatePanningDidEndInView:selectedView point:touch.location]; + }]; + } else { + [self sdl_notifyDelegatePanningDidEndInView:nil point:touch.location]; + } } } break; case SDLPerformingTouchTypeSingleTouch: { @@ -341,10 +355,13 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { CGPoint centerPoint = CGPointCenterOfPoints(touch.location, self.singleTapTouch.location); if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveDoubleTapForView:atPoint:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:centerPoint] : nil; - [self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:hitView atPoint:centerPoint]; - }); + if (self.hitTester) { + [self.hitTester viewForPoint:centerPoint selectedViewHandler:^(UIView * _Nullable selectedView) { + [self sdl_notifyDelegateDoubleTapForView:selectedView point:centerPoint]; + }]; + } else { + [self sdl_notifyDelegateDoubleTapForView:nil point:centerPoint]; + } } } @@ -430,10 +447,13 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { strongSelf.singleTapTouch = nil; [strongSelf sdl_cancelSingleTapTimer]; if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapForView:atPoint:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:point] : nil; - [strongSelf.touchEventDelegate touchManager:strongSelf didReceiveSingleTapForView:hitView atPoint:point]; - }); + if (strongSelf.hitTester) { + [strongSelf.hitTester viewForPoint:point selectedViewHandler:^(UIView * _Nullable selectedView) { + [self sdl_notifyDelegateSingleTapForView:selectedView point:point]; + }]; + } else { + [self sdl_notifyDelegateSingleTapForView:nil point:point]; + } } }); } @@ -449,6 +469,31 @@ - (void)sdl_cancelSingleTapTimer { self.singleTapTimer = NULL; } +- (void)sdl_notifyDelegateSingleTapForView:(nullable UIView *)view point:(CGPoint)point { + [self.touchEventDelegate touchManager:self didReceiveSingleTapForView:view atPoint:point]; +} + +- (void)sdl_notifyDelegateDoubleTapForView:(nullable UIView *)view point:(CGPoint)point { + [self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:view atPoint:point]; +} + +- (void)sdl_notifyDelegatePanningDidStartInView:(nullable UIView *)view point:(CGPoint)point { + [self.touchEventDelegate touchManager:self panningDidStartInView:view atPoint:point]; +} + +- (void)sdl_notifyDelegatePanningDidEndInView:(nullable UIView *)view point:(CGPoint)point { + [self.touchEventDelegate touchManager:self panningDidEndInView:view atPoint:point]; +} + +- (void)sdl_notifyDelegatePinchBeganAtCenterPoint:(CGPoint)centerPoint view:(nullable UIView *)view { + [self.touchEventDelegate touchManager:self pinchDidStartInView:view atCenterPoint:centerPoint]; +} + +- (void)sdl_notifyDelegatePinchDidEndInView:(nullable UIView *)view centerPoint:(CGPoint)centerPoint { + [self.touchEventDelegate touchManager:self pinchDidEndInView:view atCenterPoint:centerPoint]; +} + @end NS_ASSUME_NONNULL_END + diff --git a/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m b/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m index c7c339ac3..70612c854 100644 --- a/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m +++ b/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m @@ -341,11 +341,13 @@ BOOL compareRectangle(SDLRectangle *sdlRectangle, CGRect cgRect) { }); it(@"should return a view object", ^{ - UIView *view1 = [hapticManager viewForPoint:CGPointMake(125, 120)]; - expect(view1).toNot(beNil()); + [hapticManager viewForPoint:CGPointMake(125, 120) selectedViewHandler:^(UIView * _Nullable selectedView) { + expect(selectedView).toNot(beNil()); + }]; - UIView* view2 = [hapticManager viewForPoint:CGPointMake(202, 249)]; - expect(view2).toNot(beNil()); + [hapticManager viewForPoint:CGPointMake(202, 249) selectedViewHandler:^(UIView * _Nullable selectedView) { + expect(selectedView).toNot(beNil()); + }]; }); }); @@ -363,8 +365,9 @@ BOOL compareRectangle(SDLRectangle *sdlRectangle, CGRect cgRect) { }); it(@"should return no view object", ^{ - UIView* view = [hapticManager viewForPoint:CGPointMake(130, 130)]; - expect(view).to(beNil()); + [hapticManager viewForPoint:CGPointMake(130, 130) selectedViewHandler:^(UIView * _Nullable selectedView) { + expect(selectedView).to(beNil()); + }]; }); }); @@ -378,10 +381,10 @@ BOOL compareRectangle(SDLRectangle *sdlRectangle, CGRect cgRect) { [hapticManager updateInterfaceLayout]; }); it(@"should return nil", ^{ - UIView* view = [hapticManager viewForPoint:CGPointMake(0, 228)]; - expect(view).to(beNil()); + [hapticManager viewForPoint:CGPointMake(0, 228) selectedViewHandler:^(UIView * _Nullable selectedView) { + expect(selectedView).to(beNil()); + }]; }); - }); }); diff --git a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m index 1981cc804..25fad6316 100644 --- a/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m +++ b/SmartDeviceLinkTests/UtilitiesSpecs/Touches/SDLTouchManagerSpec.m @@ -94,9 +94,6 @@ @interface SDLTouchManager () __block void (^performTouchEvent)(SDLTouchManager* touchManager, SDLOnTouchEvent* onTouchEvent) = ^(SDLTouchManager* touchManager, SDLOnTouchEvent* onTouchEvent) { SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveTouchEventNotification object:nil rpcNotification:onTouchEvent]; [[NSNotificationCenter defaultCenter] postNotification:notification]; - - // Since notifications are sent to te SDLTouchManagerDelegate observers on the main thread, force the block to execute manually on the main thread. If this is not done, the test cases may fail. - [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; }; beforeEach(^{ From d1e4fd03083218f3c2be650a91572a35d3477a98 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Mon, 1 Apr 2019 15:41:11 -0400 Subject: [PATCH 04/11] Delegate notified of hit views on original thread --- SmartDeviceLink/SDLTouchManager.m | 32 ++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 35a1f0cf0..87dd8f035 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -228,8 +228,11 @@ - (void)sdl_handleTouchBegan:(SDLTouch *)touch { self.previousPinchDistance = self.currentPinchGesture.distance; if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidStartInView:atCenterPoint:)]) { if (self.hitTester) { + NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; [self.hitTester viewForPoint:self.currentPinchGesture.center selectedViewHandler:^(UIView * _Nullable selectedView) { - [self sdl_notifyDelegatePinchBeganAtCenterPoint:self.currentPinchGesture.center view:selectedView]; + dispatch_async(currentQueue.underlyingQueue, ^{ + [self sdl_notifyDelegatePinchBeganAtCenterPoint:self.currentPinchGesture.center view:selectedView]; + }); }]; } else { [self sdl_notifyDelegatePinchBeganAtCenterPoint:self.currentPinchGesture.center view:nil]; @@ -282,8 +285,11 @@ - (void)sdl_handleTouchMoved:(SDLTouch *)touch { _performingTouchType = SDLPerformingTouchTypePanningTouch; if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartInView:atPoint:)]) { if (self.hitTester) { + NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; [self.hitTester viewForPoint:touch.location selectedViewHandler:^(UIView * _Nullable selectedView) { - [self sdl_notifyDelegatePanningDidStartInView:selectedView point:touch.location]; + dispatch_async(currentQueue.underlyingQueue, ^{ + [self sdl_notifyDelegatePanningDidStartInView:selectedView point:touch.location]; + }); }]; } else { [self sdl_notifyDelegatePanningDidStartInView:nil point:touch.location]; @@ -314,9 +320,12 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { if (self.currentPinchGesture.isValid) { if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndInView:atCenterPoint:)]) { if (self.hitTester) { + NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; [self.hitTester viewForPoint:self.currentPinchGesture.center selectedViewHandler:^(UIView * _Nullable selectedView) { - [self sdl_notifyDelegatePinchDidEndInView:selectedView centerPoint:self.currentPinchGesture.center]; - self.currentPinchGesture = nil; + dispatch_async(currentQueue.underlyingQueue, ^{ + [self sdl_notifyDelegatePinchDidEndInView:selectedView centerPoint:self.currentPinchGesture.center]; + self.currentPinchGesture = nil; + }); }]; } else { [self sdl_notifyDelegatePinchDidEndInView:nil centerPoint:self.currentPinchGesture.center]; @@ -330,8 +339,11 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { case SDLPerformingTouchTypePanningTouch: { if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndInView:atPoint:)]) { if (self.hitTester) { + NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; [self.hitTester viewForPoint:touch.location selectedViewHandler:^(UIView * _Nullable selectedView) { - [self sdl_notifyDelegatePanningDidEndInView:selectedView point:touch.location]; + dispatch_async(currentQueue.underlyingQueue, ^{ + [self sdl_notifyDelegatePanningDidEndInView:selectedView point:touch.location]; + }); }]; } else { [self sdl_notifyDelegatePanningDidEndInView:nil point:touch.location]; @@ -356,8 +368,11 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { self.singleTapTouch.location); if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveDoubleTapForView:atPoint:)]) { if (self.hitTester) { + NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; [self.hitTester viewForPoint:centerPoint selectedViewHandler:^(UIView * _Nullable selectedView) { - [self sdl_notifyDelegateDoubleTapForView:selectedView point:centerPoint]; + dispatch_async(currentQueue.underlyingQueue, ^{ + [self sdl_notifyDelegateDoubleTapForView:selectedView point:centerPoint]; + }); }]; } else { [self sdl_notifyDelegateDoubleTapForView:nil point:centerPoint]; @@ -448,8 +463,11 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { [strongSelf sdl_cancelSingleTapTimer]; if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapForView:atPoint:)]) { if (strongSelf.hitTester) { + NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; [strongSelf.hitTester viewForPoint:point selectedViewHandler:^(UIView * _Nullable selectedView) { - [self sdl_notifyDelegateSingleTapForView:selectedView point:point]; + dispatch_async(currentQueue.underlyingQueue, ^{ + [self sdl_notifyDelegateSingleTapForView:selectedView point:point]; + }); }]; } else { [self sdl_notifyDelegateSingleTapForView:nil point:point]; From 1a44df8377904de637ec22998359ed50a6e67438 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Mon, 1 Apr 2019 16:25:06 -0400 Subject: [PATCH 05/11] Fixed timer thread --- SmartDeviceLink/SDLTouchManager.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 87dd8f035..61334fbf2 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -463,9 +463,10 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { [strongSelf sdl_cancelSingleTapTimer]; if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapForView:atPoint:)]) { if (strongSelf.hitTester) { - NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; + dispatch_queue_t currentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, + 0); [strongSelf.hitTester viewForPoint:point selectedViewHandler:^(UIView * _Nullable selectedView) { - dispatch_async(currentQueue.underlyingQueue, ^{ + dispatch_async(currentQueue, ^{ [self sdl_notifyDelegateSingleTapForView:selectedView point:point]; }); }]; From be232a4bec911f9d33b01885c368b27b5d574bf3 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Tue, 2 Apr 2019 09:11:32 -0400 Subject: [PATCH 06/11] Made some hit test fixes for single tap --- SmartDeviceLink/SDLFocusableItemLocator.m | 10 ++++++++++ SmartDeviceLink/SDLTouchManager.m | 4 +--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/SmartDeviceLink/SDLFocusableItemLocator.m b/SmartDeviceLink/SDLFocusableItemLocator.m index 79838f089..7cfe90d07 100644 --- a/SmartDeviceLink/SDLFocusableItemLocator.m +++ b/SmartDeviceLink/SDLFocusableItemLocator.m @@ -122,6 +122,16 @@ - (void)sdl_sendHapticRPC { #pragma mark SDLFocusableItemHitTester functions - (void)viewForPoint:(CGPoint)point selectedViewHandler:(nullable void (^)(UIView * _Nullable))selectedViewHandler { + if (NSThread.currentThread.isMainThread) { + return [self sdl_viewForPoint:point selectedViewHandler:selectedViewHandler]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + return [self sdl_viewForPoint:point selectedViewHandler:selectedViewHandler]; + }); +} + +- (void)sdl_viewForPoint:(CGPoint)point selectedViewHandler:(nullable void (^)(UIView * _Nullable))selectedViewHandler { dispatch_async(dispatch_get_main_queue(), ^{ UIView *selectedView = nil; diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 61334fbf2..03c78f9c1 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -463,10 +463,8 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { [strongSelf sdl_cancelSingleTapTimer]; if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapForView:atPoint:)]) { if (strongSelf.hitTester) { - dispatch_queue_t currentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, - 0); [strongSelf.hitTester viewForPoint:point selectedViewHandler:^(UIView * _Nullable selectedView) { - dispatch_async(currentQueue, ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self sdl_notifyDelegateSingleTapForView:selectedView point:point]; }); }]; From 04fd7bbf73ed09019c03f19d2ea5bfdb08810e17 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Tue, 2 Apr 2019 11:24:08 -0400 Subject: [PATCH 07/11] Single tap hit view now checked on main thread --- SmartDeviceLink/SDLFocusableItemHitTester.h | 4 +- SmartDeviceLink/SDLFocusableItemLocator.m | 44 ++--- SmartDeviceLink/SDLTouchManager.m | 99 ++--------- .../ProxySpecs/SDLHapticManagerSpec.m | 167 +++++++++--------- 4 files changed, 112 insertions(+), 202 deletions(-) diff --git a/SmartDeviceLink/SDLFocusableItemHitTester.h b/SmartDeviceLink/SDLFocusableItemHitTester.h index 8a94f3714..7dd7662b4 100644 --- a/SmartDeviceLink/SDLFocusableItemHitTester.h +++ b/SmartDeviceLink/SDLFocusableItemHitTester.h @@ -11,8 +11,6 @@ NS_ASSUME_NONNULL_BEGIN -typedef void (^SDLFocusableItemHitTesterSelectedViewHandler)(UIView * __nullable selectedView); - @protocol SDLFocusableItemHitTester /** @@ -21,7 +19,7 @@ typedef void (^SDLFocusableItemHitTesterSelectedViewHandler)(UIView * __nullable @param point Point to check for a view @return point UIView object or nil */ -- (void)viewForPoint:(CGPoint)point selectedViewHandler:(nullable SDLFocusableItemHitTesterSelectedViewHandler)selectedViewHandler; +- (nullable UIView *)viewForPoint:(CGPoint)point; @end diff --git a/SmartDeviceLink/SDLFocusableItemLocator.m b/SmartDeviceLink/SDLFocusableItemLocator.m index 7cfe90d07..e3fb4d451 100644 --- a/SmartDeviceLink/SDLFocusableItemLocator.m +++ b/SmartDeviceLink/SDLFocusableItemLocator.m @@ -105,7 +105,7 @@ - (void)sdl_sendHapticRPC { } NSMutableArray *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}; @@ -115,42 +115,30 @@ - (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 -- (void)viewForPoint:(CGPoint)point selectedViewHandler:(nullable void (^)(UIView * _Nullable))selectedViewHandler { - if (NSThread.currentThread.isMainThread) { - return [self sdl_viewForPoint:point selectedViewHandler:selectedViewHandler]; - } +- (nullable UIView *)viewForPoint:(CGPoint)point { + UIView *selectedView = nil; - dispatch_async(dispatch_get_main_queue(), ^{ - return [self sdl_viewForPoint:point selectedViewHandler:selectedViewHandler]; - }); -} - -- (void)sdl_viewForPoint:(CGPoint)point selectedViewHandler:(nullable void (^)(UIView * _Nullable))selectedViewHandler { - dispatch_async(dispatch_get_main_queue(), ^{ - 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]; - if ([view pointInside:localPoint withEvent:nil]) { - if (selectedView != nil) { - selectedView = nil; - break; - //the point has been indentified in two views. We cannot identify which with confidence. - } else { - selectedView = view; - } + 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]; + if ([view pointInside:localPoint withEvent:nil]) { + if (selectedView != nil) { + selectedView = nil; + break; + //the point has been indentified in two views. We cannot identify which with confidence. + } else { + selectedView = view; } } + } - return selectedViewHandler(selectedView); - }); + return selectedView; } #pragma mark notifications diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 03c78f9c1..4788ab03a 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -227,22 +227,13 @@ - (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:)]) { - if (self.hitTester) { - NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; - [self.hitTester viewForPoint:self.currentPinchGesture.center selectedViewHandler:^(UIView * _Nullable selectedView) { - dispatch_async(currentQueue.underlyingQueue, ^{ - [self sdl_notifyDelegatePinchBeganAtCenterPoint:self.currentPinchGesture.center view:selectedView]; - }); - }]; - } else { - [self sdl_notifyDelegatePinchBeganAtCenterPoint:self.currentPinchGesture.center view:nil]; - } + UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:self.currentPinchGesture.center] : nil; + [self.touchEventDelegate touchManager:self pinchDidStartInView:hitView atCenterPoint:self.currentPinchGesture.center]; } } break; } } - /** * Handles a MOVE touch event sent by Core * @@ -284,16 +275,8 @@ - (void)sdl_handleTouchMoved:(SDLTouch *)touch { _performingTouchType = SDLPerformingTouchTypePanningTouch; if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidStartInView:atPoint:)]) { - if (self.hitTester) { - NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; - [self.hitTester viewForPoint:touch.location selectedViewHandler:^(UIView * _Nullable selectedView) { - dispatch_async(currentQueue.underlyingQueue, ^{ - [self sdl_notifyDelegatePanningDidStartInView:selectedView point:touch.location]; - }); - }]; - } else { - [self sdl_notifyDelegatePanningDidStartInView:nil point: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: { @@ -319,18 +302,9 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { [self sdl_setMultiTouchFingerTouchForTouch:touch]; if (self.currentPinchGesture.isValid) { if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:pinchDidEndInView:atCenterPoint:)]) { - if (self.hitTester) { - NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; - [self.hitTester viewForPoint:self.currentPinchGesture.center selectedViewHandler:^(UIView * _Nullable selectedView) { - dispatch_async(currentQueue.underlyingQueue, ^{ - [self sdl_notifyDelegatePinchDidEndInView:selectedView centerPoint:self.currentPinchGesture.center]; - self.currentPinchGesture = nil; - }); - }]; - } else { - [self sdl_notifyDelegatePinchDidEndInView:nil centerPoint: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; } @@ -338,16 +312,8 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { } break; case SDLPerformingTouchTypePanningTouch: { if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:panningDidEndInView:atPoint:)]) { - if (self.hitTester) { - NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; - [self.hitTester viewForPoint:touch.location selectedViewHandler:^(UIView * _Nullable selectedView) { - dispatch_async(currentQueue.underlyingQueue, ^{ - [self sdl_notifyDelegatePanningDidEndInView:selectedView point:touch.location]; - }); - }]; - } else { - [self sdl_notifyDelegatePanningDidEndInView:nil point: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: { @@ -367,16 +333,8 @@ - (void)sdl_handleTouchEnded:(SDLTouch *)touch { CGPoint centerPoint = CGPointCenterOfPoints(touch.location, self.singleTapTouch.location); if ([self.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveDoubleTapForView:atPoint:)]) { - if (self.hitTester) { - NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; - [self.hitTester viewForPoint:centerPoint selectedViewHandler:^(UIView * _Nullable selectedView) { - dispatch_async(currentQueue.underlyingQueue, ^{ - [self sdl_notifyDelegateDoubleTapForView:selectedView point:centerPoint]; - }); - }]; - } else { - [self sdl_notifyDelegateDoubleTapForView:nil point:centerPoint]; - } + UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:centerPoint] : nil; + [self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:hitView atPoint:centerPoint]; } } @@ -462,15 +420,8 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { strongSelf.singleTapTouch = nil; [strongSelf sdl_cancelSingleTapTimer]; if ([strongSelf.touchEventDelegate respondsToSelector:@selector(touchManager:didReceiveSingleTapForView:atPoint:)]) { - if (strongSelf.hitTester) { - [strongSelf.hitTester viewForPoint:point selectedViewHandler:^(UIView * _Nullable selectedView) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self sdl_notifyDelegateSingleTapForView:selectedView point:point]; - }); - }]; - } else { - [self sdl_notifyDelegateSingleTapForView:nil point:point]; - } + UIView *hitView = (self.hitTester != nil) ? [self.hitTester viewForPoint:point] : nil; + [strongSelf.touchEventDelegate touchManager:strongSelf didReceiveSingleTapForView:hitView atPoint:point]; } }); } @@ -486,30 +437,6 @@ - (void)sdl_cancelSingleTapTimer { self.singleTapTimer = NULL; } -- (void)sdl_notifyDelegateSingleTapForView:(nullable UIView *)view point:(CGPoint)point { - [self.touchEventDelegate touchManager:self didReceiveSingleTapForView:view atPoint:point]; -} - -- (void)sdl_notifyDelegateDoubleTapForView:(nullable UIView *)view point:(CGPoint)point { - [self.touchEventDelegate touchManager:self didReceiveDoubleTapForView:view atPoint:point]; -} - -- (void)sdl_notifyDelegatePanningDidStartInView:(nullable UIView *)view point:(CGPoint)point { - [self.touchEventDelegate touchManager:self panningDidStartInView:view atPoint:point]; -} - -- (void)sdl_notifyDelegatePanningDidEndInView:(nullable UIView *)view point:(CGPoint)point { - [self.touchEventDelegate touchManager:self panningDidEndInView:view atPoint:point]; -} - -- (void)sdl_notifyDelegatePinchBeganAtCenterPoint:(CGPoint)centerPoint view:(nullable UIView *)view { - [self.touchEventDelegate touchManager:self pinchDidStartInView:view atCenterPoint:centerPoint]; -} - -- (void)sdl_notifyDelegatePinchDidEndInView:(nullable UIView *)view centerPoint:(CGPoint)centerPoint { - [self.touchEventDelegate touchManager:self pinchDidEndInView:view atCenterPoint:centerPoint]; -} - @end NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m b/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m index 70612c854..497870b9d 100644 --- a/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m +++ b/SmartDeviceLinkTests/ProxySpecs/SDLHapticManagerSpec.m @@ -32,23 +32,23 @@ BOOL compareRectangle(SDLRectangle *sdlRectangle, CGRect cgRect) { describe(@"the haptic manager", ^{ __block UIWindow *uiWindow; __block UIViewController *uiViewController; - + __block SDLFocusableItemLocator *hapticManager; __block SDLSendHapticData* sentHapticRequest; - + __block id sdlLifecycleManager = OCMClassMock([SDLLifecycleManager class]); __block CGRect viewRect1; __block CGRect viewRect2; - + beforeEach(^{ hapticManager = nil; sentHapticRequest = nil; - + uiWindow = [[UIWindow alloc] init]; uiViewController = [[UIViewController alloc] init]; uiWindow.rootViewController = uiViewController; - + OCMExpect([[sdlLifecycleManager stub] sendConnectionManagerRequest:[OCMArg checkWithBlock:^BOOL(id value){ BOOL isFirstArg = [value isKindOfClass:[SDLSendHapticData class]]; if(isFirstArg) { @@ -75,216 +75,216 @@ BOOL compareRectangle(SDLRectangle *sdlRectangle, CGRect cgRect) { expect(sentHapticRequest).to(beNil()); }); }); - + context(@"when initialized with no focusable view", ^{ beforeEach(^{ hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; [hapticManager updateInterfaceLayout]; }); - + it(@"should have no focusable view", ^{ OCMVerify(sdlLifecycleManager); expect(sentHapticRequest.hapticRectData.count).to(equal(0)); }); }); - + context(@"when initialized with single view", ^{ beforeEach(^{ viewRect1 = CGRectMake(101, 101, 50, 50); UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1]; [uiViewController.view addSubview:textField1]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; }); - + it(@"should have one view", ^{ OCMVerify(sdlLifecycleManager); - + int expectedCount = 1; expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount)); - + if(sentHapticRequest.hapticRectData.count == expectedCount) { NSArray *hapticRectData = sentHapticRequest.hapticRectData; SDLHapticRect *sdlhapticRect = hapticRectData[0]; SDLRectangle *sdlRect = sdlhapticRect.rect; - + compareRectangle(sdlRect, viewRect1); } }); }); - + context(@"when initialized with single button view", ^{ beforeEach(^{ viewRect1 = CGRectMake(101, 101, 50, 50); UIButton *button = [[UIButton alloc] initWithFrame:viewRect1]; [uiViewController.view addSubview:button]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; }); - + it(@"should have one view", ^{ OCMVerify(sdlLifecycleManager); - + int expectedCount = 1; expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount)); - + if(sentHapticRequest.hapticRectData.count == expectedCount) { NSArray *hapticRectData = sentHapticRequest.hapticRectData; SDLHapticRect *sdlhapticRect = hapticRectData[0]; SDLRectangle *sdlRect = sdlhapticRect.rect; - + compareRectangle(sdlRect, viewRect1); } }); }); - + context(@"when initialized with no views and then updated with two additional views", ^{ beforeEach(^{ hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; - + viewRect1 = CGRectMake(101, 101, 50, 50); UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1]; [uiViewController.view addSubview:textField1]; - + viewRect2 = CGRectMake(201, 201, 50, 50); UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2]; [uiViewController.view addSubview:textField2]; - + [hapticManager updateInterfaceLayout]; }); - + it(@"should have two views", ^{ OCMVerify(sdlLifecycleManager); - + int expectedCount = 2; expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount)); - + if(sentHapticRequest.hapticRectData.count == expectedCount) { NSArray *hapticRectData = sentHapticRequest.hapticRectData; SDLHapticRect *sdlhapticRect1 = hapticRectData[0]; SDLRectangle *sdlRect1 = sdlhapticRect1.rect; - + SDLHapticRect *sdlhapticRect2 = hapticRectData[1]; SDLRectangle *sdlRect2 = sdlhapticRect2.rect; - + compareRectangle(sdlRect1, viewRect2); compareRectangle(sdlRect2, viewRect1); } }); }); - + context(@"when initialized with nested views", ^{ beforeEach(^{ UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(101, 101, 50, 50)]; [uiViewController.view addSubview:textField]; - + viewRect1 = CGRectMake(110, 110, 10, 10); UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1]; [textField addSubview:textField1]; - + viewRect2 = CGRectMake(130, 130, 10, 10); UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2]; [textField addSubview:textField2]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; }); - + it(@"should have only leaf views added", ^{ OCMVerify(sdlLifecycleManager); - + int expectedCount = 2; expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount)); - + if(sentHapticRequest.hapticRectData.count == expectedCount) { NSArray *hapticRectData = sentHapticRequest.hapticRectData; SDLHapticRect *sdlhapticRect1 = hapticRectData[0]; SDLRectangle *sdlRect1 = sdlhapticRect1.rect; - + SDLHapticRect *sdlhapticRect2 = hapticRectData[1]; SDLRectangle *sdlRect2 = sdlhapticRect2.rect; - + compareRectangle(sdlRect1, viewRect1); compareRectangle(sdlRect2, viewRect2); } }); }); - + context(@"when initialized with nested button views", ^{ beforeEach(^{ UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(101, 101, 50, 50)]; [uiViewController.view addSubview:button]; - + viewRect1 = CGRectMake(110, 110, 10, 10); UIButton *button1 = [[UIButton alloc] initWithFrame:viewRect1]; [button addSubview:button1]; - + viewRect2 = CGRectMake(130, 130, 10, 10); UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2]; [button addSubview:textField2]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; }); - + it(@"should have only leaf views added", ^{ OCMVerify(sdlLifecycleManager); - + int expectedCount = 2; expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount)); - + if(sentHapticRequest.hapticRectData.count == expectedCount) { NSArray *hapticRectData = sentHapticRequest.hapticRectData; SDLHapticRect *sdlhapticRect1 = hapticRectData[0]; SDLRectangle *sdlRect1 = sdlhapticRect1.rect; - + SDLHapticRect *sdlhapticRect2 = hapticRectData[1]; SDLRectangle *sdlRect2 = sdlhapticRect2.rect; - + compareRectangle(sdlRect1, viewRect1); compareRectangle(sdlRect2, viewRect2); } }); }); - + context(@"when initialized with two views and then updated with one view removed", ^{ beforeEach(^{ viewRect1 = CGRectMake(101, 101, 50, 50); UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1]; [uiViewController.view addSubview:textField1]; - + viewRect2 = CGRectMake(201, 201, 50, 50); UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2]; [uiViewController.view addSubview:textField2]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; - + [textField2 removeFromSuperview]; - + [hapticManager updateInterfaceLayout]; }); - + it(@"should have one view", ^{ OCMVerify(sdlLifecycleManager); - + int expectedCount = 1; expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount)); - + if(sentHapticRequest.hapticRectData.count == expectedCount) { NSArray *hapticRectData = sentHapticRequest.hapticRectData; SDLHapticRect *sdlhapticRect = hapticRectData[0]; SDLRectangle *sdlRect = sdlhapticRect.rect; - + compareRectangle(sdlRect, viewRect1); } }); @@ -295,96 +295,93 @@ BOOL compareRectangle(SDLRectangle *sdlRectangle, CGRect cgRect) { viewRect1 = CGRectMake(101, 101, 50, 50); UITextField *textField1 = [[UITextField alloc] initWithFrame:viewRect1]; [uiViewController.view addSubview:textField1]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; - + viewRect2 = CGRectMake(201, 201, 50, 50); UITextField *textField2 = [[UITextField alloc] initWithFrame:viewRect2]; [uiViewController.view addSubview:textField2]; - + [[NSNotificationCenter defaultCenter] postNotificationName:SDLDidUpdateProjectionView object:nil]; }); - + it(@"should have two views", ^{ OCMVerify(sdlLifecycleManager); - + int expectedCount = 2; expect(sentHapticRequest.hapticRectData.count).to(equal(expectedCount)); - + if(sentHapticRequest.hapticRectData.count == expectedCount) { NSArray *hapticRectData = sentHapticRequest.hapticRectData; SDLHapticRect *sdlhapticRect1 = hapticRectData[0]; SDLRectangle *sdlRect1 = sdlhapticRect1.rect; - + SDLHapticRect *sdlhapticRect2 = hapticRectData[1]; SDLRectangle *sdlRect2 = sdlhapticRect2.rect; - + compareRectangle(sdlRect1, viewRect2); compareRectangle(sdlRect2, viewRect1); } }); }); - + context(@"when touched inside a view", ^{ beforeEach(^{ UITextField *textField1 = [[UITextField alloc] initWithFrame:CGRectMake(101, 101, 50, 50)]; [uiViewController.view addSubview:textField1]; - + UITextField *textField2 = [[UITextField alloc] initWithFrame:CGRectMake(201, 201, 50, 50)]; [uiViewController.view addSubview:textField2]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; }); - + it(@"should return a view object", ^{ - [hapticManager viewForPoint:CGPointMake(125, 120) selectedViewHandler:^(UIView * _Nullable selectedView) { - expect(selectedView).toNot(beNil()); - }]; + UIView *view1 = [hapticManager viewForPoint:CGPointMake(125, 120)]; + expect(view1).toNot(beNil()); - [hapticManager viewForPoint:CGPointMake(202, 249) selectedViewHandler:^(UIView * _Nullable selectedView) { - expect(selectedView).toNot(beNil()); - }]; + UIView* view2 = [hapticManager viewForPoint:CGPointMake(202, 249)]; + expect(view2).toNot(beNil()); }); }); - + context(@"when touched in overlapping views' area", ^{ beforeEach(^{ UITextField *textField1 = [[UITextField alloc] initWithFrame:CGRectMake(101, 101, 50, 50)]; [uiViewController.view addSubview:textField1]; - + UITextField *textField2 = [[UITextField alloc] initWithFrame:CGRectMake(126, 126, 50, 50)]; [uiViewController.view addSubview:textField2]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; }); - + it(@"should return no view object", ^{ - [hapticManager viewForPoint:CGPointMake(130, 130) selectedViewHandler:^(UIView * _Nullable selectedView) { - expect(selectedView).to(beNil()); - }]; + UIView* view = [hapticManager viewForPoint:CGPointMake(130, 130)]; + expect(view).to(beNil()); }); }); - + context(@"when touched outside view boundary", ^{ beforeEach(^{ UITextField *textField1 = [[UITextField alloc] initWithFrame:CGRectMake(101, 101, 50, 50)]; [uiWindow insertSubview:textField1 aboveSubview:uiWindow]; - + hapticManager = [[SDLFocusableItemLocator alloc] initWithViewController:uiViewController connectionManager:sdlLifecycleManager]; hapticManager.enableHapticDataRequests = YES; [hapticManager updateInterfaceLayout]; }); it(@"should return nil", ^{ - [hapticManager viewForPoint:CGPointMake(0, 228) selectedViewHandler:^(UIView * _Nullable selectedView) { - expect(selectedView).to(beNil()); - }]; + UIView* view = [hapticManager viewForPoint:CGPointMake(0, 228)]; + expect(view).to(beNil()); }); + }); }); From 95e7547b92b78e8e806c674369f26558cede59d1 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Tue, 2 Apr 2019 11:28:23 -0400 Subject: [PATCH 08/11] Single tap hit view is now done on main thread checking if single tap hit a view is now done on main thread --- SmartDeviceLink/SDLTouchManager.m | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 4788ab03a..a6d220d1a 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -420,12 +420,27 @@ - (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]; + }]; } }); } +- (void)sdl_getSingleTapHitView:(CGPoint)point hitViewHandler:(nullable void (^)(UIView * __nullable hitView))hitViewSelector { + if (!self.hitTester) { + return hitViewSelector(nil); + } + + NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; + dispatch_async(dispatch_get_main_queue(), ^{ + UIView *hitView = [self.hitTester viewForPoint:point]; + dispatch_async(currentQueue.underlyingQueue, ^{ + return hitViewSelector(hitView); + }); + }); +} + /** * Cancels a tap gesture timer */ @@ -440,4 +455,3 @@ - (void)sdl_cancelSingleTapTimer { @end NS_ASSUME_NONNULL_END - From d7a01bf05963865a3a92b4c4e119afa5d5b27dbe Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Tue, 2 Apr 2019 11:45:38 -0400 Subject: [PATCH 09/11] Fixed single tap hit view return queue --- SmartDeviceLink/SDLTouchManager.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index a6d220d1a..650277558 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -432,10 +432,9 @@ - (void)sdl_getSingleTapHitView:(CGPoint)point hitViewHandler:(nullable void (^) return hitViewSelector(nil); } - NSOperationQueue *currentQueue = NSOperationQueue.currentQueue; dispatch_async(dispatch_get_main_queue(), ^{ UIView *hitView = [self.hitTester viewForPoint:point]; - dispatch_async(currentQueue.underlyingQueue, ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ return hitViewSelector(hitView); }); }); From 677703d71dadf8cc6358cc44a366f48efd2868f5 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Tue, 2 Apr 2019 13:08:36 -0400 Subject: [PATCH 10/11] Fixed parameter name in `SDLTouchManager` --- SmartDeviceLink/SDLTouchManager.m | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 650277558..5ae0d33c3 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -427,15 +427,23 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)point { }); } -- (void)sdl_getSingleTapHitView:(CGPoint)point hitViewHandler:(nullable void (^)(UIView * __nullable hitView))hitViewSelector { +/** + * 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. This is done to ma + * + * @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 a view + */ +- (void)sdl_getSingleTapHitView:(CGPoint)point hitViewHandler:(nullable void (^)(UIView * __nullable hitView))hitViewHandler { if (!self.hitTester) { - return hitViewSelector(nil); + 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), ^{ - return hitViewSelector(hitView); + return hitViewHandler(hitView); }); }); } From b067fc9a5b1af505f138918da1fdfcb14cbb9926 Mon Sep 17 00:00:00 2001 From: NicoleYarroch Date: Tue, 2 Apr 2019 15:47:19 -0400 Subject: [PATCH 11/11] Added check for nil hitViewHandler in Touch Manager * fixed documentation --- SmartDeviceLink/SDLTouchManager.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SmartDeviceLink/SDLTouchManager.m b/SmartDeviceLink/SDLTouchManager.m index 5ae0d33c3..9a66d81e4 100644 --- a/SmartDeviceLink/SDLTouchManager.m +++ b/SmartDeviceLink/SDLTouchManager.m @@ -430,19 +430,21 @@ - (void)sdl_initializeSingleTapTimerAtPoint:(CGPoint)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. This is done to ma + * 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 a view + * @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); }); });