summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2017-02-09 13:49:04 -0800
committerFabian Guerra Soto <fabian.guerra@mapbox.com>2017-02-09 16:49:04 -0500
commit272dc3f6a91ee1f0734c6642d610366f4396ec93 (patch)
tree9c5f89949ea38cd27cbc1c2eff89109e4c3a40c9
parentb6e5edc26844df6a5f1f6e34881826828d36ce6a (diff)
downloadqtlocation-mapboxgl-272dc3f6a91ee1f0734c6642d610366f4396ec93.tar.gz
Delegate method to restrict movement (#5584)
* [ios, macos] Added delegate method to restrict movement Added a way for the delegate to restrict where the user can move within the map using gestures. Fixes #2457. * [ios] Added support to restrict movement in pinch/rotate gestures * [ios] Added support to restrict movement in double tap/quick zoom/two finger drag gestures * [ios] fixed camera reset before two finger drag gesture is complete * [ios] fixed camera comparison in double tap gestures * [ios] Changelog update * [macos] Changelog updated * [ios, macos] Changelog cleanup * [ios, macos] Added documentation to clarify performance impact * [ios] clarified variable name * [ios] blocking gestures implementation changed to a predictive approach * [ios] gesture delegate methods refactoring * [ios] Removed duplicated methods, improved code readability * [ios] code refactoring to clarify the conditions to execute a gesture
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/ios/src/MGLMapView.mm244
-rw-r--r--platform/ios/src/MGLMapViewDelegate.h22
-rw-r--r--platform/macos/CHANGELOG.md1
-rw-r--r--platform/macos/src/MGLMapView.mm32
-rw-r--r--platform/macos/src/MGLMapViewDelegate.h22
6 files changed, 270 insertions, 52 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 53a0538506..5b4a81c6e3 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -37,6 +37,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* Fixed an issue where translucent, non-view-backed point annotations along tile boundaries would be drawn darker than expected. ([#6832](https://github.com/mapbox/mapbox-gl-native/pull/6832))
* Fixed flickering that occurred when panning past the antimeridian. ([#7574](https://github.com/mapbox/mapbox-gl-native/pull/7574))
* Added a `MGLDistanceFormatter` class for formatting geographic distances. ([#7888](https://github.com/mapbox/mapbox-gl-native/pull/7888))
+* Added a method to MGLMapViewDelegate, `-mapView:shouldChangeFromCamera:toCamera:`, that you can implement to restrict which parts the user can navigate to using gestures. ([#5584](https://github.com/mapbox/mapbox-gl-native/pull/5584))
## 3.4.1 - January 25, 2017
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 0c438d5606..cc86ed1e53 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -1205,6 +1205,8 @@ public:
_mbglMap->cancelTransitions();
+ MGLMapCamera *oldCamera = self.camera;
+
if (pan.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGesturePanStart forRecognizer:pan];
@@ -1216,8 +1218,15 @@ public:
else if (pan.state == UIGestureRecognizerStateChanged)
{
CGPoint delta = [pan translationInView:pan.view];
- _mbglMap->moveBy({ delta.x, delta.y });
- [pan setTranslation:CGPointZero inView:pan.view];
+
+ MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:delta panGesture:pan];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ {
+ _mbglMap->moveBy({ delta.x, delta.y });
+ [pan setTranslation:CGPointZero inView:pan.view];
+ }
[self notifyMapChange:mbgl::MapChangeRegionIsChanging];
}
@@ -1234,7 +1243,13 @@ public:
if (drift)
{
CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, velocity.y * self.decelerationRate / 4);
- _mbglMap->moveBy({ offset.x, offset.y }, MGLDurationInSecondsFromTimeInterval(self.decelerationRate));
+ MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ {
+ _mbglMap->moveBy({ offset.x, offset.y }, MGLDurationInSecondsFromTimeInterval(self.decelerationRate));
+ }
}
[self notifyGestureDidEndWithDrift:drift];
@@ -1250,6 +1265,7 @@ public:
MGLEventKeyZoomLevel: @(zoom)
}];
}
+
}
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch
@@ -1261,6 +1277,7 @@ public:
_mbglMap->cancelTransitions();
CGPoint centerPoint = [self anchorPointForGesture:pinch];
+ MGLMapCamera *oldCamera = self.camera;
if (pinch.state == UIGestureRecognizerStateBegan)
{
@@ -1273,11 +1290,17 @@ public:
else if (pinch.state == UIGestureRecognizerStateChanged)
{
CGFloat newScale = self.scale * pinch.scale;
-
- if (log2(newScale) < _mbglMap->getMinZoom()) return;
-
- _mbglMap->setScale(newScale, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
-
+ double zoom = log2(newScale);
+ if (zoom < _mbglMap->getMinZoom()) return;
+
+ // Calculates the final camera zoom, has no effect within current map camera.
+ MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ {
+ _mbglMap->setScale(newScale, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+ }
// The gesture recognizer only reports the gesture’s current center
// point, so use the previous center point to anchor the transition.
// If the number of touches has changed, the remembered center point is
@@ -1288,7 +1311,6 @@ public:
_mbglMap->setLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate),
mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
}
-
[self notifyMapChange:mbgl::MapChangeRegionIsChanging];
}
else if (pinch.state == UIGestureRecognizerStateEnded || pinch.state == UIGestureRecognizerStateCancelled)
@@ -1321,14 +1343,25 @@ public:
{
velocity = 0;
}
-
- if (velocity && duration)
+
+ BOOL drift = velocity && duration;
+
+ // Calculates the final camera zoom, this has no effect within current map camera.
+ double zoom = log2(newScale);
+ MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint];
+
+ if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]
+ && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
- _mbglMap->setScale(newScale, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationInSecondsFromTimeInterval(duration));
+ drift = NO;
+ } else {
+ if (drift)
+ {
+ _mbglMap->setScale(newScale, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationInSecondsFromTimeInterval(duration));
+ }
}
- [self notifyGestureDidEndWithDrift:velocity && duration];
-
+ [self notifyGestureDidEndWithDrift:drift];
[self unrotateIfNeededForGesture];
}
@@ -1343,7 +1376,8 @@ public:
_mbglMap->cancelTransitions();
CGPoint centerPoint = [self anchorPointForGesture:rotate];
-
+ MGLMapCamera *oldCamera = self.camera;
+
if (rotate.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGestureRotateStart forRecognizer:rotate];
@@ -1368,10 +1402,17 @@ public:
newDegrees = fminf(newDegrees, 30);
newDegrees = fmaxf(newDegrees, -30);
}
-
- _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
-
+
+ MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ {
+ _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+ }
+
[self notifyMapChange:mbgl::MapChangeRegionIsChanging];
+
}
else if (rotate.state == UIGestureRecognizerStateEnded || rotate.state == UIGestureRecognizerStateCancelled)
{
@@ -1383,16 +1424,22 @@ public:
CGFloat newRadians = radians + velocity * decelerationRate * 0.1;
CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1;
- _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationInSecondsFromTimeInterval(decelerationRate));
-
- [self notifyGestureDidEndWithDrift:YES];
-
- __weak MGLMapView *weakSelf = self;
-
- [self animateWithDelay:decelerationRate animations:^
- {
- [weakSelf unrotateIfNeededForGesture];
- }];
+ MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ {
+ _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationInSecondsFromTimeInterval(decelerationRate));
+
+ [self notifyGestureDidEndWithDrift:YES];
+
+ __weak MGLMapView *weakSelf = self;
+
+ [self animateWithDelay:decelerationRate animations:^
+ {
+ [weakSelf unrotateIfNeededForGesture];
+ }];
+ }
}
else
{
@@ -1508,18 +1555,27 @@ public:
if (doubleTap.state == UIGestureRecognizerStateEnded)
{
- [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap];
+ MGLMapCamera *oldCamera = self.camera;
+
CGPoint gesturePoint = [self anchorPointForGesture:doubleTap];
-
- mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
- _mbglMap->scaleBy(2, center, MGLDurationInSecondsFromTimeInterval(MGLAnimationDuration));
-
- __weak MGLMapView *weakSelf = self;
-
- [self animateWithDelay:MGLAnimationDuration animations:^
+
+ MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:self.zoomLevel + 1.0 aroundAnchorPoint:gesturePoint];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
- [weakSelf unrotateIfNeededForGesture];
- }];
+ [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap];
+
+ mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
+ _mbglMap->scaleBy(2, center, MGLDurationInSecondsFromTimeInterval(MGLAnimationDuration));
+
+ __weak MGLMapView *weakSelf = self;
+
+ [self animateWithDelay:MGLAnimationDuration animations:^
+ {
+ [weakSelf unrotateIfNeededForGesture];
+ }];
+ }
}
}
@@ -1537,17 +1593,26 @@ public:
}
else if (twoFingerTap.state == UIGestureRecognizerStateEnded)
{
- CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap];
+ MGLMapCamera *oldCamera = self.camera;
- mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
- _mbglMap->scaleBy(0.5, center, MGLDurationInSecondsFromTimeInterval(MGLAnimationDuration));
-
- __weak MGLMapView *weakSelf = self;
-
- [self animateWithDelay:MGLAnimationDuration animations:^
+ double zoom = self.zoomLevel;
+ CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap];
+
+ MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom - 1.0 aroundAnchorPoint:gesturePoint];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
- [weakSelf unrotateIfNeededForGesture];
- }];
+ mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
+ _mbglMap->scaleBy(0.5, center, MGLDurationInSecondsFromTimeInterval(MGLAnimationDuration));
+
+ __weak MGLMapView *weakSelf = self;
+
+ [self animateWithDelay:MGLAnimationDuration animations:^
+ {
+ [weakSelf unrotateIfNeededForGesture];
+ }];
+ }
}
}
@@ -1556,7 +1621,7 @@ public:
if ( ! self.isZoomEnabled) return;
_mbglMap->cancelTransitions();
-
+
if (quickZoom.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGestureQuickZoom forRecognizer:quickZoom];
@@ -1576,9 +1641,21 @@ public:
if (newZoom < _mbglMap->getMinZoom()) return;
CGPoint centerPoint = [self anchorPointForGesture:quickZoom];
-
- _mbglMap->scaleBy(powf(2, newZoom) / _mbglMap->getScale(),
- mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+
+ MGLMapCamera *oldCamera = self.camera;
+
+ double zoom = self.zoomLevel;
+ double scale = powf(2, newZoom) / _mbglMap->getScale();
+
+ double estimatedZoom = zoom * scale;
+
+ MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:estimatedZoom aroundAnchorPoint:centerPoint];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ {
+ _mbglMap->scaleBy(scale, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+ }
[self notifyMapChange:mbgl::MapChangeRegionIsChanging];
}
@@ -1594,6 +1671,7 @@ public:
if ( ! self.isPitchEnabled) return;
_mbglMap->cancelTransitions();
+ MGLMapCamera *oldCamera = self.camera;
if (twoFingerDrag.state == UIGestureRecognizerStateBegan)
{
@@ -1610,7 +1688,13 @@ public:
CGPoint centerPoint = [self anchorPointForGesture:twoFingerDrag];
- _mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+ MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew];
+
+ if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
+ [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ {
+ _mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+ }
[self notifyMapChange:mbgl::MapChangeRegionIsChanging];
}
@@ -1619,6 +1703,62 @@ public:
[self notifyGestureDidEndWithDrift:NO];
[self unrotateIfNeededForGesture];
}
+
+}
+
+- (MGLMapCamera *)cameraByPanningWithTranslation:(CGPoint)endPoint panGesture:(UIPanGestureRecognizer *)pan
+{
+ MGLMapCamera *panCamera = [self.camera copy];
+
+ CGPoint centerPoint = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
+ CGPoint endCameraPoint = CGPointMake(centerPoint.x - endPoint.x, centerPoint.y - endPoint.y);
+ CLLocationCoordinate2D panCoordinate = [self convertPoint:endCameraPoint toCoordinateFromView:pan.view];
+
+ panCamera.centerCoordinate = panCoordinate;
+
+ return panCamera;
+}
+
+- (MGLMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint
+{
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
+ mbgl::CameraOptions currentCameraOptions = _mbglMap->getCameraOptions(padding);
+ MGLMapCamera *camera;
+
+ mbgl::ScreenCoordinate anchor = mbgl::ScreenCoordinate { anchorPoint.x, anchorPoint.y };
+ currentCameraOptions.zoom = mbgl::util::clamp(zoom, self.minimumZoomLevel, self.maximumZoomLevel);
+ currentCameraOptions.anchor = anchor;
+ camera = [self cameraForCameraOptions:currentCameraOptions];
+
+ return camera;
+}
+
+- (MGLMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees aroundAnchorPoint:(CGPoint)anchorPoint
+{
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
+ mbgl::CameraOptions currentCameraOptions = _mbglMap->getCameraOptions(padding);
+
+ MGLMapCamera *camera;
+
+ mbgl::ScreenCoordinate anchor = mbgl::ScreenCoordinate { anchorPoint.x, anchorPoint.y };
+ currentCameraOptions.angle = degrees * mbgl::util::DEG2RAD;
+ currentCameraOptions.anchor = anchor;
+ camera = [self cameraForCameraOptions:currentCameraOptions];
+
+ return camera;
+}
+
+- (MGLMapCamera *)cameraByTiltingToPitch:(CGFloat)pitch
+{
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
+ mbgl::CameraOptions currentCameraOptions = _mbglMap->getCameraOptions(padding);
+
+ MGLMapCamera *camera;
+
+ currentCameraOptions.pitch = pitch * mbgl::util::DEG2RAD;
+ camera = [self cameraForCameraOptions:currentCameraOptions];
+
+ return camera;
}
- (CGPoint)anchorPointForGesture:(UIGestureRecognizer *)gesture {
diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h
index 8a57e15f7a..12320a63a5 100644
--- a/platform/ios/src/MGLMapViewDelegate.h
+++ b/platform/ios/src/MGLMapViewDelegate.h
@@ -59,6 +59,28 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
+/**
+ Asks the delegate whether the map view should be allowed to change from the
+ existing camera to the new camera in response to a user gesture.
+
+ This method is called as soon as the user gesture is recognized. It is not
+ called in response to a programmatic camera change, such as by setting the
+ `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
+
+ This method is called many times during gesturing, so you should avoid performing
+ complex or performance-intensive tasks in your implementation.
+
+ @param mapView The map view that the user is manipulating.
+ @param oldCamera The camera representing the viewpoint at the moment the
+ gesture is recognized. If this method returns `NO`, the map view’s camera
+ continues to be this camera.
+ @param newCamera The expected camera after the gesture completes. If this
+ method returns `YES`, this camera becomes the map view’s camera.
+ @return A Boolean value indicating whether the map view should stay at
+ `oldCamera` or change to `newCamera`.
+ */
+- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera;
+
#pragma mark Loading the Map
/**
diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md
index eb06c36e36..dc2c93a64a 100644
--- a/platform/macos/CHANGELOG.md
+++ b/platform/macos/CHANGELOG.md
@@ -34,6 +34,7 @@
* Fixed an issue where translucent point annotations along tile boundaries would be drawn darker than expected. ([#6832](https://github.com/mapbox/mapbox-gl-native/pull/6832))
* Fixed flickering that occurred when panning past the antimeridian. ([#7574](https://github.com/mapbox/mapbox-gl-native/pull/7574))
+* Added a method to MGLMapViewDelegate, `-mapView:shouldChangeFromCamera:toCamera:`, that you can implement to restrict which parts the user can navigate to using gestures. ([#5584](https://github.com/mapbox/mapbox-gl-native/pull/5584))
* Added a `MGLDistanceFormatter` class for formatting geographic distances. ([#7888](https://github.com/mapbox/mapbox-gl-native/pull/7888))
## 0.3.1
diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm
index d687a19aed..3d5f71feb1 100644
--- a/platform/macos/src/MGLMapView.mm
+++ b/platform/macos/src/MGLMapView.mm
@@ -995,8 +995,13 @@ public:
- (void)offsetCenterCoordinateBy:(NSPoint)delta animated:(BOOL)animated {
[self willChangeValueForKey:@"centerCoordinate"];
_mbglMap->cancelTransitions();
+ MGLMapCamera *oldCamera = self.camera;
_mbglMap->moveBy({ delta.x, delta.y },
MGLDurationInSecondsFromTimeInterval(animated ? MGLAnimationDuration : 0));
+ if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]
+ && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) {
+ self.camera = oldCamera;
+ }
[self didChangeValueForKey:@"centerCoordinate"];
}
@@ -1043,8 +1048,13 @@ public:
- (void)scaleBy:(double)scaleFactor atPoint:(NSPoint)point animated:(BOOL)animated {
[self willChangeValueForKey:@"centerCoordinate"];
[self willChangeValueForKey:@"zoomLevel"];
+ MGLMapCamera *oldCamera = self.camera;
mbgl::ScreenCoordinate center(point.x, self.bounds.size.height - point.y);
_mbglMap->scaleBy(scaleFactor, center, MGLDurationInSecondsFromTimeInterval(animated ? MGLAnimationDuration : 0));
+ if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]
+ && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) {
+ self.camera = oldCamera;
+ }
[self didChangeValueForKey:@"zoomLevel"];
[self didChangeValueForKey:@"centerCoordinate"];
}
@@ -1379,15 +1389,25 @@ public:
_directionAtBeginningOfGesture = self.direction;
_pitchAtBeginningOfGesture = _mbglMap->getPitch();
} else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ MGLMapCamera *oldCamera = self.camera;
+ BOOL didChangeCamera = NO;
mbgl::ScreenCoordinate center(startPoint.x, self.bounds.size.height - startPoint.y);
if (self.rotateEnabled) {
CLLocationDirection newDirection = _directionAtBeginningOfGesture - delta.x / 10;
[self willChangeValueForKey:@"direction"];
_mbglMap->setBearing(newDirection, center);
+ didChangeCamera = YES;
[self didChangeValueForKey:@"direction"];
}
if (self.pitchEnabled) {
_mbglMap->setPitch(_pitchAtBeginningOfGesture + delta.y / 5, center);
+ didChangeCamera = YES;
+ }
+
+ if (didChangeCamera
+ && [self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]
+ && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) {
+ self.camera = oldCamera;
}
}
} else if (self.scrollEnabled) {
@@ -1427,7 +1447,12 @@ public:
if (gestureRecognizer.magnification > -1) {
[self willChangeValueForKey:@"zoomLevel"];
[self willChangeValueForKey:@"centerCoordinate"];
+ MGLMapCamera *oldCamera = self.camera;
_mbglMap->setScale(_scaleAtBeginningOfGesture * (1 + gestureRecognizer.magnification), center);
+ if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]
+ && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) {
+ self.camera = oldCamera;
+ }
[self didChangeValueForKey:@"centerCoordinate"];
[self didChangeValueForKey:@"zoomLevel"];
}
@@ -1502,9 +1527,16 @@ public:
_mbglMap->setGestureInProgress(true);
_directionAtBeginningOfGesture = self.direction;
} else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ MGLMapCamera *oldCamera = self.camera;
+
NSPoint rotationPoint = [gestureRecognizer locationInView:self];
mbgl::ScreenCoordinate center(rotationPoint.x, self.bounds.size.height - rotationPoint.y);
_mbglMap->setBearing(_directionAtBeginningOfGesture + gestureRecognizer.rotationInDegrees, center);
+
+ if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]
+ && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) {
+ self.camera = oldCamera;
+ }
} else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
|| gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
_mbglMap->setGestureInProgress(false);
diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h
index 534e28e3a8..08a9f7eff4 100644
--- a/platform/macos/src/MGLMapViewDelegate.h
+++ b/platform/macos/src/MGLMapViewDelegate.h
@@ -61,6 +61,28 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)mapView:(MGLMapView *)mapView cameraDidChangeAnimated:(BOOL)animated;
+/**
+ Asks the delegate whether the map view should be allowed to change from the
+ existing camera to the new camera in response to a user gesture.
+
+ This method is called as soon as the user gesture is recognized. It is not
+ called in response to a programmatic camera change, such as by setting the
+ `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
+
+ This method is called many times during gesturing, so you should avoid performing
+ complex or performance-intensive tasks in your implementation.
+
+ @param mapView The map view that the user is manipulating.
+ @param oldCamera The camera representing the viewpoint at the moment the
+ gesture is recognized. If this method returns `NO`, the map view’s camera
+ continues to be this camera.
+ @param newCamera The expected camera after the gesture completes. If this
+ method returns `YES`, this camera becomes the map view’s camera.
+ @return A Boolean value indicating whether the map view should stay at
+ `oldCamera` or change to `newCamera`.
+ */
+- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera;
+
#pragma mark Loading the Map
/**