diff options
author | Julian Rex <julian.rex@mapbox.com> | 2018-02-08 00:35:21 -0500 |
---|---|---|
committer | Julian Rex <julian.rex@mapbox.com> | 2018-02-09 16:33:29 -0500 |
commit | 5b6252ff06a3f2bdfd2c87789bceb79882cb1cef (patch) | |
tree | c4407ec1196f9e38fcb7536a28455dbd200d901c | |
parent | 5ad584af1b041591e2d5f2bb12364b0a890107c5 (diff) | |
download | qtlocation-mapboxgl-5b6252ff06a3f2bdfd2c87789bceb79882cb1cef.tar.gz |
[ios] Added:
- MGLCameraChangeReason constants
- New camera change delegate methods with reason parameters.
- mapView:didSingleTapAtCoordinate: delegate method
-rw-r--r-- | platform/ios/app/MBXViewController.m | 24 | ||||
-rw-r--r-- | platform/ios/ios.xcodeproj/project.pbxproj | 24 | ||||
-rw-r--r-- | platform/ios/src/MGLCameraChange.h | 14 | ||||
-rw-r--r-- | platform/ios/src/MGLCameraChange.m | 16 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 196 | ||||
-rw-r--r-- | platform/ios/src/MGLMapViewDelegate.h | 97 | ||||
-rw-r--r-- | platform/ios/test/MGLMapViewDelegateIntegrationTests.swift | 6 | ||||
-rw-r--r-- | platform/ios/uitest/MapViewTests.m | 7 |
8 files changed, 329 insertions, 55 deletions
diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 2c3d26b489..d452d152cc 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -1279,6 +1279,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { - (void)styleDynamicPointCollection { [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(36.9979, -109.0441) zoomLevel:14 animated:NO]; + CLLocationCoordinate2D coordinates[] = { {37.00145594210082, -109.04960632324219}, {37.00173012609867, -109.0404224395752}, @@ -1879,15 +1880,34 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { _usingLocaleBasedCountryLabels = [[self bestLanguageForUser] isEqualToString:@"en"]; } -- (void)mapViewRegionIsChanging:(MGLMapView *)mapView +- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera reason:(MGLCameraChangeReason)reason +{ + return YES; +} + +- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated reason:(MGLCameraChangeReason)reason +{ +} + +- (void)mapViewRegionIsChanging:(MGLMapView *)mapView reason:(MGLCameraChangeReason)reason { [self updateHUD]; } -- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { +- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated reason:(MGLCameraChangeReason)reason +{ [self updateHUD]; } +- (void)mapView:(MGLMapView *)mapView didSingleTapAtCoordinate:(CLLocationCoordinate2D)coordinate +{ +#if 0 + MGLPointAnnotation *annot = [[MGLPointAnnotation alloc] init]; + annot.coordinate = coordinate; + [self.mapView addAnnotation:annot]; +#endif +} + - (void)mapView:(MGLMapView *)mapView didUpdateUserLocation:(MGLUserLocation *)userLocation { [self updateHUD]; } diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 2325f3d3ce..1f193f0623 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -251,6 +251,10 @@ AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; }; AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; + CA55CD3F202C16AA00CE7095 /* MGLCameraChange.m in Sources */ = {isa = PBXBuildFile; fileRef = CA55CD3D202C16A900CE7095 /* MGLCameraChange.m */; }; + CA55CD40202C16AA00CE7095 /* MGLCameraChange.m in Sources */ = {isa = PBXBuildFile; fileRef = CA55CD3D202C16A900CE7095 /* MGLCameraChange.m */; }; + CA55CD41202C16AA00CE7095 /* MGLCameraChange.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChange.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA55CD42202C16AA00CE7095 /* MGLCameraChange.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChange.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */; }; @@ -756,6 +760,8 @@ 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = "<group>"; }; AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = "<group>"; }; AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = "<group>"; }; + CA55CD3D202C16A900CE7095 /* MGLCameraChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLCameraChange.m; sourceTree = "<group>"; }; + CA55CD3E202C16AA00CE7095 /* MGLCameraChange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChange.h; sourceTree = "<group>"; }; DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = "<group>"; }; DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = "<group>"; }; DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; }; @@ -1449,19 +1455,21 @@ DA8848331CBAFB2A00AB86E3 /* Kit */ = { isa = PBXGroup; children = ( - 355ADFF91E9281C300F3939D /* Views */, - 35CE617F1D4165C2004F2359 /* Categories */, DAD165841CF4D06B001FF4B9 /* Annotations */, + 35CE617F1D4165C2004F2359 /* Categories */, + DA88487F1CBB033F00AB86E3 /* Fabric */, + DA8848881CBB036000AB86E3 /* SMCalloutView */, DAD165851CF4D08B001FF4B9 /* Telemetry */, + 355ADFF91E9281C300F3939D /* Views */, + CA55CD3E202C16AA00CE7095 /* MGLCameraChange.h */, + CA55CD3D202C16A900CE7095 /* MGLCameraChange.m */, DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */, DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */, - DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */, + DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, + DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */, DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, - DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, - DA88487F1CBB033F00AB86E3 /* Fabric */, - DA8848881CBB036000AB86E3 /* SMCalloutView */, ); name = Kit; path = src; @@ -1764,6 +1772,7 @@ 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */, 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */, + CA55CD41202C16AA00CE7095 /* MGLCameraChange.h in Headers */, 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */, DA88483B1CBAFB8500AB86E3 /* MGLCalloutView.h in Headers */, 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */, @@ -1896,6 +1905,7 @@ 3510FFF11D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, 35D3A1E71E9BE7EC002B38EE /* MGLScaleBar.h in Headers */, 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */, + CA55CD42202C16AA00CE7095 /* MGLCameraChange.h in Headers */, DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */, DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */, 927FBD001F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */, @@ -2407,6 +2417,7 @@ 35136D3F1D42273000C20EFD /* MGLLineStyleLayer.mm in Sources */, DA704CC41F65A475004B3F28 /* MGLMapAccessibilityElement.mm in Sources */, DA72620D1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, + CA55CD3F202C16AA00CE7095 /* MGLCameraChange.m in Sources */, DA88481A1CBAFA6200AB86E3 /* MGLAccountManager.m in Sources */, 3510FFFB1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */, AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */, @@ -2497,6 +2508,7 @@ 35136D401D42273000C20EFD /* MGLLineStyleLayer.mm in Sources */, DA704CC51F65A475004B3F28 /* MGLMapAccessibilityElement.mm in Sources */, DA72620E1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, + CA55CD40202C16AA00CE7095 /* MGLCameraChange.m in Sources */, DAA4E42F1CBB730400178DFB /* MGLCompactCalloutView.m in Sources */, 3510FFFC1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */, AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */, diff --git a/platform/ios/src/MGLCameraChange.h b/platform/ios/src/MGLCameraChange.h new file mode 100644 index 0000000000..bfd956d66d --- /dev/null +++ b/platform/ios/src/MGLCameraChange.h @@ -0,0 +1,14 @@ +#import "MGLFoundation.h" + +typedef NSString *MGLCameraChangeReason NS_TYPED_ENUM; + +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonUnknown; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonProgramatic; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonGestureResetNorth; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonGesturePan; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonGesturePinch; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonGestureRotate; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonGestureDoubleTap; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonGestureTwoFingerSingleTap; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonGestureQuickZoom; +extern MGL_EXPORT const MGLCameraChangeReason MGLCameraChangeReasonGesturePitchStart; diff --git a/platform/ios/src/MGLCameraChange.m b/platform/ios/src/MGLCameraChange.m new file mode 100644 index 0000000000..02f00fc990 --- /dev/null +++ b/platform/ios/src/MGLCameraChange.m @@ -0,0 +1,16 @@ +#import "MGLCameraChange.h" + +#define MGL_CAMERA_CHANGE_REASON_DEF(name) const MGLCameraChangeReason name = @ #name + +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonUnknown); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonProgramatic); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonGestureResetNorth); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonGesturePan); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonGesturePinch); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonGestureRotate); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonGestureDoubleTap); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonGestureTwoFingerSingleTap); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonGestureQuickZoom); +MGL_CAMERA_CHANGE_REASON_DEF(MGLCameraChangeReasonGesturePitchStart); + +#undef MGL_CAMERA_CHANGE_REASON_DEF diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 84afc56223..dae9988c79 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -209,6 +209,8 @@ public: @property (nonatomic) UILongPressGestureRecognizer *quickZoom; @property (nonatomic) UIPanGestureRecognizer *twoFingerDrag; +@property (nonatomic) MGLCameraChangeReason cameraChangeReason; + /// Mapping from reusable identifiers to annotation images. @property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImagesByIdentifier; @@ -535,13 +537,14 @@ public: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationDidBecomeActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationWillResignActiveNotification object:nil]; // $$jr missing??? [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + // set initial position // mbgl::CameraOptions options; @@ -549,6 +552,9 @@ public: mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset); options.padding = padding; options.zoom = 0; + + _cameraChangeReason = MGLCameraChangeReasonUnknown; + _mbglMap->jumpTo(options); _pendingLatitude = NAN; _pendingLongitude = NAN; @@ -1235,6 +1241,8 @@ public: - (void)handleCompassTapGesture:(__unused id)sender { + self.cameraChangeReason = MGLCameraChangeReasonGestureResetNorth; + [self resetNorthAnimated:YES]; if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading || @@ -1257,6 +1265,7 @@ public: - (void)notifyGestureDidBegin { BOOL animated = NO; + [self cameraWillChangeAnimated:animated]; _mbglMap->setGestureInProgress(true); _changeDelimiterSuppressionDepth++; @@ -1280,6 +1289,23 @@ public: return _changeDelimiterSuppressionDepth > 0; } +- (BOOL)_shouldChangeFromCamera:(nonnull MGLMapCamera *)oldCamera toCamera:(nonnull MGLMapCamera *)newCamera +{ + // Check delegates first + if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:reason:)]) + { + return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera reason:self.cameraChangeReason]; + } + else if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]) + { + return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera]; + } + else + { + return YES; + } +} + - (void)handlePanGesture:(UIPanGestureRecognizer *)pan { if ( ! self.isScrollEnabled) return; @@ -1287,7 +1313,9 @@ public: _mbglMap->cancelTransitions(); MGLMapCamera *oldCamera = self.camera; - + + self.cameraChangeReason = MGLCameraChangeReasonGesturePan; + if (pan.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePanStart forRecognizer:pan]; @@ -1301,9 +1329,8 @@ public: CGPoint delta = [pan translationInView: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]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->moveBy({ delta.x, delta.y }); [pan setTranslation:CGPointZero inView:pan.view]; @@ -1325,9 +1352,8 @@ public: { CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, velocity.y * self.decelerationRate / 4); MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->moveBy({ offset.x, offset.y }, MGLDurationFromTimeInterval(self.decelerationRate)); } @@ -1358,6 +1384,8 @@ public: CGPoint centerPoint = [self anchorPointForGesture:pinch]; MGLMapCamera *oldCamera = self.camera; + self.cameraChangeReason = MGLCameraChangeReasonGesturePinch; + if (pinch.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePinchStart forRecognizer:pinch]; @@ -1374,9 +1402,8 @@ public: // Calculates the final camera zoom, has no effect within current map camera. MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); // The gesture recognizer only reports the gesture’s current center @@ -1428,9 +1455,8 @@ public: // 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]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { drift = NO; } else { @@ -1456,7 +1482,9 @@ public: CGPoint centerPoint = [self anchorPointForGesture:rotate]; MGLMapCamera *oldCamera = self.camera; - + + self.cameraChangeReason = MGLCameraChangeReasonGestureRotate; + if (rotate.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureRotateStart forRecognizer:rotate]; @@ -1483,9 +1511,8 @@ public: } MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -1503,9 +1530,8 @@ public: CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1; MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationFromTimeInterval(decelerationRate)); @@ -1550,6 +1576,8 @@ public: } [self deselectAnnotation:self.selectedAnnotation animated:YES]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement); + + // Should we call mapView:didSingleTapAtCoordinate: here? If so, what coordinate? return; } @@ -1560,10 +1588,21 @@ public: CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint]; [self selectAnnotation:annotation animated:YES calloutPositioningRect:positionRect]; } - else + else if (self.selectedAnnotation) { [self deselectAnnotation:self.selectedAnnotation animated:YES]; } + else + { + // An annotation wasn't selected or deselected. + if ([self.delegate respondsToSelector:@selector(mapView:didSingleTapAtCoordinate:)]) + { + CGPoint pointInView = [singleTap locationInView:singleTap.view]; + CLLocationCoordinate2D tapCoordinate = [self convertPoint:pointInView toCoordinateFromView:singleTap.view]; + + [self.delegate mapView:self didSingleTapAtCoordinate:tapCoordinate]; + } + } } /** @@ -1642,6 +1681,8 @@ public: if (doubleTap.state == UIGestureRecognizerStateEnded) { + self.cameraChangeReason = MGLCameraChangeReasonGestureDoubleTap; + MGLMapCamera *oldCamera = self.camera; double newZoom = round(self.zoomLevel) + 1.0; @@ -1649,9 +1690,8 @@ public: CGPoint gesturePoint = [self anchorPointForGesture:doubleTap]; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap]; @@ -1678,9 +1718,13 @@ public: _mbglMap->cancelTransitions(); + self.cameraChangeReason = MGLCameraChangeReasonGestureTwoFingerSingleTap; + if (twoFingerTap.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureTwoFingerSingleTap forRecognizer:twoFingerTap]; + + [self notifyGestureDidBegin]; } else if (twoFingerTap.state == UIGestureRecognizerStateEnded) { @@ -1691,9 +1735,8 @@ public: CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap]; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); _mbglMap->setZoom(newZoom, center, MGLDurationFromTimeInterval(MGLAnimationDuration)); @@ -1713,7 +1756,9 @@ public: if ( ! self.isZoomEnabled) return; _mbglMap->cancelTransitions(); - + + self.cameraChangeReason = MGLCameraChangeReasonGestureQuickZoom; + if (quickZoom.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureQuickZoom forRecognizer:quickZoom]; @@ -1736,9 +1781,8 @@ public: MGLMapCamera *oldCamera = self.camera; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -1758,6 +1802,8 @@ public: _mbglMap->cancelTransitions(); + self.cameraChangeReason = MGLCameraChangeReasonGesturePitchStart; + if (twoFingerDrag.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePitchStart forRecognizer:twoFingerDrag]; @@ -1777,8 +1823,7 @@ public: MGLMapCamera *oldCamera = self.camera; MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew]; - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -1936,7 +1981,8 @@ public: { id<MGLAnnotation>annotation = [self annotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer persistingResults:NO]; if(!annotation) { - return NO; + // Only allow this gesture to be recognized if the delegate implements the single tap method + return [self.delegate respondsToSelector:@selector(mapView:didSingleTapAtCoordinate:)]; } } } @@ -2854,6 +2900,8 @@ public: { self.userTrackingMode = MGLUserTrackingModeNone; + self.cameraChangeReason = MGLCameraChangeReasonProgramatic; + [self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion]; } @@ -2903,6 +2951,9 @@ public: } _mbglMap->cancelTransitions(); + + self.cameraChangeReason = MGLCameraChangeReasonProgramatic; + _mbglMap->easeTo(cameraOptions, animationOptions); } @@ -2926,6 +2977,8 @@ public: if (zoomLevel == self.zoomLevel) return; _mbglMap->cancelTransitions(); + self.cameraChangeReason = MGLCameraChangeReasonProgramatic; + CGFloat duration = animated ? MGLAnimationDuration : 0; _mbglMap->setZoom(zoomLevel, @@ -3014,6 +3067,9 @@ public: - (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { self.userTrackingMode = MGLUserTrackingModeNone; + + self.cameraChangeReason = MGLCameraChangeReasonProgramatic; + [self _setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:completion]; } @@ -3063,6 +3119,9 @@ public: [self willChangeValueForKey:@"visibleCoordinateBounds"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReason = MGLCameraChangeReasonProgramatic; + _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"visibleCoordinateBounds"]; } @@ -3096,6 +3155,8 @@ public: CGFloat duration = animated ? MGLAnimationDuration : 0; + self.cameraChangeReason = MGLCameraChangeReasonProgramatic; + if (self.userTrackingMode == MGLUserTrackingModeNone) { _mbglMap->setBearing(direction, @@ -3180,6 +3241,9 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReason = MGLCameraChangeReasonProgramatic; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; @@ -3236,6 +3300,9 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReason = MGLCameraChangeReasonProgramatic; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets]; _mbglMap->flyTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; @@ -3396,6 +3463,26 @@ public: return [self metersPerPointAtLatitude:latitude]; } +#pragma mark - Camera Change Reason - + +- (void)resetCameraChangeReason +{ + self.cameraChangeReason = MGLCameraChangeReasonUnknown; +} + +- (void)setCameraChangeReason:(MGLCameraChangeReason)reason +{ + // If not already set, update. This is required since the reason can be set in succession (for + // example "resetting north" sets the reason to MGLCameraChangeReasonGestureResetNorth before + // calling a public method which calls self.cameraChangeReason with MGLCameraChangeReasonProgramatic. + // We want the first reason to win out. This may be better handled with a stack or some other + // mechanism. + if (MGLCameraChangeReasonUnknown == _cameraChangeReason) + { + _cameraChangeReason = reason; + } +} + #pragma mark - Styling - - (NS_ARRAY_OF(NSURL *) *)bundledStyleURLs @@ -5342,9 +5429,16 @@ public: } } - if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) + if ( ! [self isSuppressingChangeDelimiters] ) { - [self.delegate mapView:self regionWillChangeAnimated:animated]; + if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:reason:)]) + { + [self.delegate mapView:self regionWillChangeAnimated:animated reason:self.cameraChangeReason]; + } + else if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) + { + [self.delegate mapView:self regionWillChangeAnimated:animated]; + } } } @@ -5358,8 +5452,12 @@ public: if (!self.scaleBar.hidden) { [(MGLScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]]; } - - if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) + + if ([self.delegate respondsToSelector:@selector(mapView:regionIsChangingWithReason:)]) + { + [self.delegate mapView:self regionIsChangingWithReason:self.cameraChangeReason]; + } + else if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { [self.delegate mapViewRegionIsChanging:self]; } @@ -5372,9 +5470,13 @@ public: [self updateCompass]; - if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) + if ( ! [self isSuppressingChangeDelimiters]) { - if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) + BOOL respondsToSelector = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]; + BOOL respondsToSelectorWithReason = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:reason:)]; + + if ((respondsToSelector || respondsToSelectorWithReason) && + ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)) { _featureAccessibilityElements = nil; _visiblePlaceFeatures = nil; @@ -5386,7 +5488,17 @@ public: UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); } } - [self.delegate mapView:self regionDidChangeAnimated:animated]; + + if (respondsToSelectorWithReason) + { + [self.delegate mapView:self regionDidChangeAnimated:animated reason:self.cameraChangeReason]; + } + else + { + [self.delegate mapView:self regionDidChangeAnimated:animated]; + } + + [self resetCameraChangeReason]; } } diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h index 096711fcbb..dd5eb68d88 100644 --- a/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/src/MGLMapViewDelegate.h @@ -1,6 +1,7 @@ #import <UIKit/UIKit.h> #import "MGLTypes.h" +#import "MGLCameraChange.h" NS_ASSUME_NONNULL_BEGIN @@ -29,10 +30,26 @@ NS_ASSUME_NONNULL_BEGIN @param mapView The map view whose viewpoint will change. @param animated Whether the change will cause an animated effect on the map. + + @note If `mapView:regionWillChangeAnimated:reason` is implemented this method will not be called. */ - (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated; /** + Tells the delegate that the viewpoint depicted by the map view is about to change. + + This method is called whenever the currently displayed map camera will start + changing for any reason. + + @param mapView The map view whose viewpoint will change. + @param animated Whether the change will cause an animated effect on the map. + @param reason The reason for the camera change. + + @note If this method is implemented `mapView:regionWillChangeAnimated:` will not be called. + */ +- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated reason:(MGLCameraChangeReason)reason; + +/** Tells the delegate that the viewpoint depicted by the map view is changing. This method is called as the currently displayed map camera changes as part of @@ -45,10 +62,31 @@ NS_ASSUME_NONNULL_BEGIN as possible to avoid affecting performance. @param mapView The map view whose viewpoint is changing. + + @note If `mapView:regionIsChangingWithReason:` is implemented this method will not be called. */ - (void)mapViewRegionIsChanging:(MGLMapView *)mapView; /** + Tells the delegate that the viewpoint depicted by the map view is changing. + + This method is called as the currently displayed map camera changes as part of + an animation, whether due to a user gesture or due to a call to a method such + as `-[MGLMapView setCamera:animated:]`. This method can be called before + `-mapViewDidFinishLoadingMap:` is called. + + During the animation, this method may be called many times to report updates to + the viewpoint. Therefore, your implementation of this method should be as lightweight + as possible to avoid affecting performance. + + @param mapView The map view whose viewpoint is changing. + @param reason The reason for the camera change. + + @note If this method is implemented `mapViewRegionIsChanging:` will not be called. + */ +- (void)mapView:(MGLMapView *)mapView regionIsChangingWithReason:(MGLCameraChangeReason)reason; + +/** Tells the delegate that the viewpoint depicted by the map view has finished changing. @@ -58,10 +96,28 @@ NS_ASSUME_NONNULL_BEGIN @param mapView The map view whose viewpoint has changed. @param animated Whether the change caused an animated effect on the map. + + @note If `mapView:regionDidChangeAnimated:reason:` is implemented this method will not be called. */ - (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated; /** + Tells the delegate that the viewpoint depicted by the map view has finished + changing. + + This method is called whenever the currently displayed map camera has finished + changing, after any calls to `-mapViewRegionIsChanging:` due to animation. Therefore, + this method can be called before `-mapViewDidFinishLoadingMap:` is called. + + @param mapView The map view whose viewpoint has changed. + @param animated Whether the change caused an animated effect on the map. + @param reason The reason for the camera change. + + @note If this method is implemented `mapView:regionDidChangeAnimated:` will not be called. + */ +- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated reason:(MGLCameraChangeReason)reason; + +/** 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. @@ -80,9 +136,50 @@ NS_ASSUME_NONNULL_BEGIN 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`. + + @note If `mapView:shouldChangeFromCamera:toCamera:reason:` is implemented this method will not be called. */ - (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera; +/** + 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. + @param reason The reason for the camera change. + @return A Boolean value indicating whether the map view should stay at + `oldCamera` or change to `newCamera`. + + @note If this method is implemented `mapView:shouldChangeFromCamera:toCamera:` will not be called. + */ +- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera reason:(MGLCameraChangeReason)reason; + +#pragma mark Responding to user gestures + +/** + Tells the delegate that the user has tapped on the map view. + + @param mapView The map view that was tapped. + @param coordinate Location of tap in world coordinates. + + @note This method will not be called when the user single taps on an annotation, or if an annotation + is currently selected. + */ +- (void)mapView:(MGLMapView *)mapView didSingleTapAtCoordinate:(CLLocationCoordinate2D)coordinate; + + #pragma mark Loading the Map /** diff --git a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift index 50f101e86b..875b4a34da 100644 --- a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift @@ -11,7 +11,7 @@ class MGLMapViewDelegateIntegrationTests: XCTestCase { extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { - func mapViewRegionIsChanging(_ mapView: MGLMapView) {} + func mapViewRegionIsChanging(_ mapView: MGLMapView, reason: MGLCameraChangeReason) {} func mapView(_ mapView: MGLMapView, didChange mode: MGLUserTrackingMode, animated: Bool) {} @@ -33,9 +33,9 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, didDeselect annotation: MGLAnnotation) {} - func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) {} + func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool, reason: MGLCameraChangeReason) {} - func mapView(_ mapView: MGLMapView, regionWillChangeAnimated animated: Bool) {} + func mapView(_ mapView: MGLMapView, regionWillChangeAnimated animated: Bool, reason: MGLCameraChangeReason) {} func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {} diff --git a/platform/ios/uitest/MapViewTests.m b/platform/ios/uitest/MapViewTests.m index 4ed3d89399..8a5521a86e 100644 --- a/platform/ios/uitest/MapViewTests.m +++ b/platform/ios/uitest/MapViewTests.m @@ -538,10 +538,13 @@ userInfo:@{ @"animated" : @(animated) }]; } -- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { +- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated reason:(MGLCameraChangeReason)reason { + [[NSNotificationCenter defaultCenter] postNotificationName:@"regionDidChangeAnimated" object:mapView - userInfo:@{ @"animated" : @(animated) }]; + userInfo:@{ @"animated" : @(animated), + @"reason" : @(reason) + }]; } - (void)testDelegatesStartStopLocatingUser { |