From 38e51a9db4513a573699373665a3d5b04f85422b Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Thu, 9 Jun 2016 16:00:47 -0400 Subject: [PATCH 1/2] [ios] Support for a customizable zoom gesture centering strategy. We introduce a new MGLZoomGestureCentering enum and corresponding zoomGestureCentering property on MGLMapView. Currently, Mapbox automatically adjusts the center of the map relative to the user's gesture location, but for some applications (e.g Uber, Lyft) it is preferable to keep the map center "locked" in response to zoom gestures. Exisiting code will retain its current behavior without modification and will implicitly use the default value of MGLZoomGestureCenteringFollowsTouch. New code can specify MGLZoomGestureCenteringLockedInPlace to get the new "locked" behavior. --- platform/ios/src/MGLMapView.h | 18 ++++++++++++++++++ platform/ios/src/MGLMapView.mm | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index 63d799bda97..432acf19f27 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -31,6 +31,17 @@ typedef NS_ENUM(NSUInteger, MGLAnnotationVerticalAlignment) { MGLAnnotationVerticalAlignmentBottom, }; +/** + How the map view should adjust its center coordinate in response to + a user-initiated zoom gesture. + */ +typedef NS_ENUM(NSUInteger, MGLZoomGestureCentering) { + /** Adjusts the center of the map relative to the user's touch position. */ + MGLZoomGestureCenteringFollowsTouch = 0, + /** Locks the zoom to the center of the map view. */ + MGLZoomGestureCenteringLockedInPlace, +}; + /** Options for enabling debugging features in an MGLMapView instance. */ typedef NS_OPTIONS(NSUInteger, MGLMapDebugMaskOptions) { /** Edges of tile boundaries are shown as thick, red lines to help diagnose @@ -300,6 +311,13 @@ IB_DESIGNABLE */ - (void)setUserLocationVerticalAlignment:(MGLAnnotationVerticalAlignment)alignment animated:(BOOL)animated; +/** + How to adjust the center coordinate of the map during a zoom operation that + occurs in response to a user gesture. The default value is + `MGLZoomGestureCenteringFollowsTouch`. + */ +@property (nonatomic, assign) MGLZoomGestureCentering zoomGestureCentering; + /** Whether the map view should display a heading calibration alert when necessary. The default value is `YES`. diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 5d1bcb11092..9720619a7ec 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1235,7 +1235,7 @@ - (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch _mbglMap->cancelTransitions(); - CGPoint centerPoint = [pinch locationInView:pinch.view]; + CGPoint centerPoint = self.zoomGestureCentering == MGLZoomGestureCenteringFollowsTouch ? [pinch locationInView:pinch.view] : self.contentCenter; if (self.userTrackingMode != MGLUserTrackingModeNone) { centerPoint = self.userLocationAnnotationViewCenter; @@ -1311,7 +1311,7 @@ - (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch [self unrotateIfNeededForGesture]; } - _previousPinchCenterCoordinate = [self convertPoint:[pinch locationInView:pinch.view] toCoordinateFromView:self]; + _previousPinchCenterCoordinate = [self convertPoint:centerPoint toCoordinateFromView:self]; _previousPinchNumberOfTouches = pinch.numberOfTouches; } @@ -1454,7 +1454,7 @@ - (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap if (doubleTap.state == UIGestureRecognizerStateEnded) { [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap]; - CGPoint gesturePoint = [doubleTap locationInView:doubleTap.view]; + CGPoint gesturePoint = self.zoomGestureCentering == MGLZoomGestureCenteringFollowsTouch ? [doubleTap locationInView:doubleTap.view] : self.contentCenter; if (self.userTrackingMode != MGLUserTrackingModeNone) { gesturePoint = self.userLocationAnnotationViewCenter; @@ -1486,7 +1486,7 @@ - (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap } else if (twoFingerTap.state == UIGestureRecognizerStateEnded) { - CGPoint gesturePoint = [twoFingerTap locationInView:twoFingerTap.view]; + CGPoint gesturePoint = self.zoomGestureCentering == MGLZoomGestureCenteringFollowsTouch ? [twoFingerTap locationInView:twoFingerTap.view] : self.contentCenter; if (self.userTrackingMode != MGLUserTrackingModeNone) { gesturePoint = self.userLocationAnnotationViewCenter; From a496b0ffab3660b67869931f0e1a45a3ee21675a Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Thu, 9 Jun 2016 18:41:41 -0400 Subject: [PATCH 2/2] [ios] Added -anchorPointForGesture: to MGLMapView --- platform/ios/src/MGLMapView.h | 37 ++++++++++++------------ platform/ios/src/MGLMapView.mm | 52 ++++++++++++++-------------------- 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index 432acf19f27..09142c40936 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -31,17 +31,6 @@ typedef NS_ENUM(NSUInteger, MGLAnnotationVerticalAlignment) { MGLAnnotationVerticalAlignmentBottom, }; -/** - How the map view should adjust its center coordinate in response to - a user-initiated zoom gesture. - */ -typedef NS_ENUM(NSUInteger, MGLZoomGestureCentering) { - /** Adjusts the center of the map relative to the user's touch position. */ - MGLZoomGestureCenteringFollowsTouch = 0, - /** Locks the zoom to the center of the map view. */ - MGLZoomGestureCenteringLockedInPlace, -}; - /** Options for enabling debugging features in an MGLMapView instance. */ typedef NS_OPTIONS(NSUInteger, MGLMapDebugMaskOptions) { /** Edges of tile boundaries are shown as thick, red lines to help diagnose @@ -311,13 +300,6 @@ IB_DESIGNABLE */ - (void)setUserLocationVerticalAlignment:(MGLAnnotationVerticalAlignment)alignment animated:(BOOL)animated; -/** - How to adjust the center coordinate of the map during a zoom operation that - occurs in response to a user gesture. The default value is - `MGLZoomGestureCenteringFollowsTouch`. - */ -@property (nonatomic, assign) MGLZoomGestureCentering zoomGestureCentering; - /** Whether the map view should display a heading calibration alert when necessary. The default value is `YES`. @@ -780,6 +762,25 @@ IB_DESIGNABLE */ - (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets; +/** + Returns the point in this view's coordinate system on which to "anchor" in + response to a user-initiated gesture. + + For example, a pinch-to-zoom gesture would anchor the map at the midpoint of + the pinch. + + If the `userTrackingMode` property is not `MGLUserTrackingModeNone`, the + user annotation is used as the anchor point. + + Subclasses may override this method to provide specialized behavior - for + example, anchoring on the map's center point to provide a "locked" zooming + mode. + + @param gesture An anchorable user gesture. + @return The point on which to anchor in response to the gesture. + */ +- (CGPoint)anchorPointForGesture:(UIGestureRecognizer *)gesture; + /** The distance from the edges of the map view’s frame to the edges of the map view’s logical viewport. diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 9720619a7ec..4e304b23a55 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1235,11 +1235,7 @@ - (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch _mbglMap->cancelTransitions(); - CGPoint centerPoint = self.zoomGestureCentering == MGLZoomGestureCenteringFollowsTouch ? [pinch locationInView:pinch.view] : self.contentCenter; - if (self.userTrackingMode != MGLUserTrackingModeNone) - { - centerPoint = self.userLocationAnnotationViewCenter; - } + CGPoint centerPoint = [self anchorPointForGesture:pinch]; if (pinch.state == UIGestureRecognizerStateBegan) { @@ -1321,11 +1317,7 @@ - (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate _mbglMap->cancelTransitions(); - CGPoint centerPoint = [rotate locationInView:rotate.view]; - if (self.userTrackingMode != MGLUserTrackingModeNone) - { - centerPoint = self.userLocationAnnotationViewCenter; - } + CGPoint centerPoint = [self anchorPointForGesture:rotate]; if (rotate.state == UIGestureRecognizerStateBegan) { @@ -1454,11 +1446,7 @@ - (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap if (doubleTap.state == UIGestureRecognizerStateEnded) { [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap]; - CGPoint gesturePoint = self.zoomGestureCentering == MGLZoomGestureCenteringFollowsTouch ? [doubleTap locationInView:doubleTap.view] : self.contentCenter; - if (self.userTrackingMode != MGLUserTrackingModeNone) - { - gesturePoint = self.userLocationAnnotationViewCenter; - } + CGPoint gesturePoint = [self anchorPointForGesture:doubleTap]; mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); _mbglMap->scaleBy(2, center, MGLDurationInSeconds(MGLAnimationDuration)); @@ -1486,11 +1474,7 @@ - (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap } else if (twoFingerTap.state == UIGestureRecognizerStateEnded) { - CGPoint gesturePoint = self.zoomGestureCentering == MGLZoomGestureCenteringFollowsTouch ? [twoFingerTap locationInView:twoFingerTap.view] : self.contentCenter; - if (self.userTrackingMode != MGLUserTrackingModeNone) - { - gesturePoint = self.userLocationAnnotationViewCenter; - } + CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap]; mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); _mbglMap->scaleBy(0.5, center, MGLDurationInSeconds(MGLAnimationDuration)); @@ -1528,11 +1512,8 @@ - (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom if (newZoom < _mbglMap->getMinZoom()) return; - CGPoint centerPoint = self.contentCenter; - if (self.userTrackingMode != MGLUserTrackingModeNone) - { - centerPoint = self.userLocationAnnotationViewCenter; - } + CGPoint centerPoint = [self anchorPointForGesture:quickZoom]; + _mbglMap->scaleBy(powf(2, newZoom) / _mbglMap->getScale(), mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); @@ -1564,11 +1545,8 @@ - (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag CGFloat pitchNew = currentPitch - (gestureDistance / slowdown); - CGPoint centerPoint = self.contentCenter; - if (self.userTrackingMode != MGLUserTrackingModeNone) - { - centerPoint = self.userLocationAnnotationViewCenter; - } + CGPoint centerPoint = [self anchorPointForGesture:twoFingerDrag]; + _mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); [self notifyMapChange:mbgl::MapChangeRegionIsChanging]; @@ -1578,7 +1556,21 @@ - (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag [self notifyGestureDidEndWithDrift:NO]; [self unrotateIfNeededForGesture]; } +} +- (CGPoint)anchorPointForGesture:(UIGestureRecognizer *)gesture { + if (self.userTrackingMode != MGLUserTrackingModeNone) + { + return self.userLocationAnnotationViewCenter; + } + + // Special case for two-finger drag and quickzoom + if ([gesture isKindOfClass:[UIPanGestureRecognizer class]] || [gesture isKindOfClass:[UILongPressGestureRecognizer class]]) + { + return self.contentCenter; + } + + return [gesture locationInView:gesture.view]; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer