summaryrefslogtreecommitdiff
path: root/platform/ios
diff options
context:
space:
mode:
authorJason Wray <friedbunny@users.noreply.github.com>2019-07-11 16:06:54 -0700
committerGitHub <noreply@github.com>2019-07-11 16:06:54 -0700
commit5b488225fb720ef607c13a33ff6509d92bb7de95 (patch)
tree6b3f61c1d27110fb3f4bd2ea51e4be5697443c22 /platform/ios
parentde71ff5102987d66ce7578216b32ae13ecf9622f (diff)
downloadqtlocation-mapboxgl-5b488225fb720ef607c13a33ff6509d92bb7de95.tar.gz
[ios] Add MGLCompassButton and MGLOrnamentVisibility
Diffstat (limited to 'platform/ios')
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj22
-rw-r--r--platform/ios/jazzy.yml1
-rw-r--r--platform/ios/sdk-files.json3
-rw-r--r--platform/ios/src/MGLCompassButton.h22
-rw-r--r--platform/ios/src/MGLCompassButton.mm128
-rw-r--r--platform/ios/src/MGLCompassButton_Private.h19
-rw-r--r--platform/ios/src/MGLMapView.h9
-rw-r--r--platform/ios/src/MGLMapView.mm105
-rw-r--r--platform/ios/src/MGLMapView_Private.h3
-rw-r--r--platform/ios/src/Mapbox.h1
-rw-r--r--platform/ios/src/UIImage+MGLAdditions.h6
-rw-r--r--platform/ios/src/UIImage+MGLAdditions.mm18
-rw-r--r--platform/ios/test/MGLCompassButtonTests.m84
-rw-r--r--platform/ios/test/MGLMapViewDirectionTests.mm33
15 files changed, 355 insertions, 100 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index cfdee6e8eb..884c966ba2 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -34,6 +34,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* `-[MGLMapView selectAnnotation:animated:completionHandler:]`
* Deprecated variants of the above methods without completion handlers. ([#14959](https://github.com/mapbox/mapbox-gl-native/pull/14959))
* Fixed an issue where the two-finger tilt gesture would continue after lifting one finger. ([#14969](https://github.com/mapbox/mapbox-gl-native/pull/14969))
+* Added `MGLMapView.compassView.visibility` and `MGLOrnamentVisibility` to allow configuration of compass visibility behavior. ([#15055](https://github.com/mapbox/mapbox-gl-native/pull/15055))
## 5.1.0 - June 19, 2019
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index fbf7974be0..670e78a46c 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -372,6 +372,8 @@
9620BB3B1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */; };
9621F2502091020E005B3800 /* NSExpression+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3510FFEE1D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
96381C0222C6F3950053497D /* MGLMapViewPitchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96381C0122C6F3950053497D /* MGLMapViewPitchTests.m */; };
+ 9641771F22D546DA00332422 /* MGLCompassButton_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9641771E22D546DA00332422 /* MGLCompassButton_Private.h */; };
+ 9641772022D546DA00332422 /* MGLCompassButton_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9641771E22D546DA00332422 /* MGLCompassButton_Private.h */; };
9654C1261FFC1AB900DB6A19 /* MGLPolyline_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C1251FFC1AB900DB6A19 /* MGLPolyline_Private.h */; };
9654C1291FFC1CCD00DB6A19 /* MGLPolygon_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C1271FFC1CC000DB6A19 /* MGLPolygon_Private.h */; };
9658C155204761FC00D8A674 /* MGLMapViewScaleBarTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9658C154204761FC00D8A674 /* MGLMapViewScaleBarTests.m */; };
@@ -424,6 +426,11 @@
96E516FB20005A4000A02306 /* MGLUserLocationHeadingBeamLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 966FCF4A1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h */; };
96E516FC20005A4400A02306 /* MGLUserLocationHeadingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */; };
96E5170420005A6B00A02306 /* SMCalloutView.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848891CBB037E00AB86E3 /* SMCalloutView.h */; };
+ 96E6145622CC135200109F14 /* MGLCompassButtonTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E6145522CC135200109F14 /* MGLCompassButtonTests.m */; };
+ 96E6145922CC169000109F14 /* MGLCompassButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 96E6145722CC169000109F14 /* MGLCompassButton.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 96E6145A22CC169000109F14 /* MGLCompassButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 96E6145722CC169000109F14 /* MGLCompassButton.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 96E6145B22CC169000109F14 /* MGLCompassButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 96E6145822CC169000109F14 /* MGLCompassButton.mm */; };
+ 96E6145C22CC169000109F14 /* MGLCompassButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 96E6145822CC169000109F14 /* MGLCompassButton.mm */; };
96ED34DE22374C0900E9FCA9 /* MGLMapViewDirectionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 96ED34DD22374C0900E9FCA9 /* MGLMapViewDirectionTests.mm */; };
96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */; };
9C188C4F2242C95A0022FA55 /* MMEDate.m in Sources */ = {isa = PBXBuildFile; fileRef = 40834BBC1FE05D6E00C1BD0D /* MMEDate.m */; };
@@ -1104,6 +1111,7 @@
9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLSDKUpdateChecker.h; sourceTree = "<group>"; };
9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = MGLSDKUpdateChecker.mm; sourceTree = "<group>"; };
96381C0122C6F3950053497D /* MGLMapViewPitchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewPitchTests.m; sourceTree = "<group>"; };
+ 9641771E22D546DA00332422 /* MGLCompassButton_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLCompassButton_Private.h; sourceTree = "<group>"; };
9654C1251FFC1AB900DB6A19 /* MGLPolyline_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLPolyline_Private.h; sourceTree = "<group>"; };
9654C1271FFC1CC000DB6A19 /* MGLPolygon_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLPolygon_Private.h; sourceTree = "<group>"; };
9658C154204761FC00D8A674 /* MGLMapViewScaleBarTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewScaleBarTests.m; sourceTree = "<group>"; };
@@ -1132,6 +1140,9 @@
96E0272C1E57C7E5004B8E66 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
96E0272D1E57C7E6004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; };
96E0272E1E57C7E7004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
+ 96E6145522CC135200109F14 /* MGLCompassButtonTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLCompassButtonTests.m; sourceTree = "<group>"; };
+ 96E6145722CC169000109F14 /* MGLCompassButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLCompassButton.h; sourceTree = "<group>"; };
+ 96E6145822CC169000109F14 /* MGLCompassButton.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLCompassButton.mm; sourceTree = "<group>"; };
96ED34DD22374C0900E9FCA9 /* MGLMapViewDirectionTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapViewDirectionTests.mm; sourceTree = "<group>"; };
96F017292118FBAE00892778 /* MGLMapView_Experimental.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapView_Experimental.h; sourceTree = "<group>"; };
96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = "<group>"; };
@@ -1658,6 +1669,9 @@
355ADFF91E9281C300F3939D /* Views */ = {
isa = PBXGroup;
children = (
+ 96E6145722CC169000109F14 /* MGLCompassButton.h */,
+ 9641771E22D546DA00332422 /* MGLCompassButton_Private.h */,
+ 96E6145822CC169000109F14 /* MGLCompassButton.mm */,
355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */,
355ADFFC1E9281DA00F3939D /* MGLScaleBar.mm */,
);
@@ -2026,6 +2040,7 @@
DA35A2C31CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m */,
35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.mm */,
DA35A2C41CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m */,
+ 96E6145522CC135200109F14 /* MGLCompassButtonTests.m */,
DA35A2A91CCA058D00E826B2 /* MGLCoordinateFormatterTests.m */,
3598544C1E1D38AA00B29F84 /* MGLDistanceFormatterTests.m */,
6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */,
@@ -2411,6 +2426,7 @@
DA35A2BB1CCA9A6900E826B2 /* MGLClockDirectionFormatter.h in Headers */,
353933FE1D3FB7DD003F57D7 /* MGLSymbolStyleLayer.h in Headers */,
DA8848201CBAFA6200AB86E3 /* MGLOfflinePack_Private.h in Headers */,
+ 9641771F22D546DA00332422 /* MGLCompassButton_Private.h in Headers */,
DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */,
DA8847FA1CBAFA5100AB86E3 /* MGLPolyline.h in Headers */,
3566C7711D4A9198008152BC /* MGLSource_Private.h in Headers */,
@@ -2454,6 +2470,7 @@
74CB5EC3219B282500102936 /* MGLCircleStyleLayer_Private.h in Headers */,
DA8847F01CBAFA5100AB86E3 /* MGLAnnotation.h in Headers */,
400533011DB0862B0069F638 /* NSArray+MGLAdditions.h in Headers */,
+ 96E6145922CC169000109F14 /* MGLCompassButton.h in Headers */,
1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */,
4049C29D1DB6CD6C00B3F799 /* MGLPointCollection.h in Headers */,
40CF6DBB1DAC3C6600A4D18B /* MGLShape_Private.h in Headers */,
@@ -2602,6 +2619,7 @@
96E516DE200054F700A02306 /* MGLGeometry_Private.h in Headers */,
353933FC1D3FB7C0003F57D7 /* MGLRasterStyleLayer.h in Headers */,
3566C76D1D4A8DFA008152BC /* MGLRasterTileSource.h in Headers */,
+ 9641772022D546DA00332422 /* MGLCompassButton_Private.h in Headers */,
DAED38641D62D0FC00D7640F /* NSURL+MGLAdditions.h in Headers */,
DABFB85E1CBE99E500D62B32 /* MGLAnnotation.h in Headers */,
DABFB8641CBE99E500D62B32 /* MGLOfflineStorage.h in Headers */,
@@ -2645,6 +2663,7 @@
9C6E283922A982670056B7BE /* MMEEventLogReportViewController.h in Headers */,
1F6A82A92138871900BA5B41 /* MGLLoggingConfiguration_Private.h in Headers */,
DABFB8661CBE99E500D62B32 /* MGLPointAnnotation.h in Headers */,
+ 96E6145A22CC169000109F14 /* MGLCompassButton.h in Headers */,
96E516E42000560B00A02306 /* MGLComputedShapeSource_Private.h in Headers */,
96E516E92000560B00A02306 /* MGLAnnotationImage_Private.h in Headers */,
96E516E52000560B00A02306 /* MGLOfflinePack_Private.h in Headers */,
@@ -3240,6 +3259,7 @@
40CFA6511D7875BB008103BD /* MGLShapeSourceTests.mm in Sources */,
DA35A2C51CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m in Sources */,
35B8E08C1D6C8B5100E768D2 /* MGLPredicateTests.mm in Sources */,
+ 96E6145622CC135200109F14 /* MGLCompassButtonTests.m in Sources */,
96036A0620059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m in Sources */,
1F95931D1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm in Sources */,
DA695426215B1E76002041A4 /* MGLMapCameraTests.m in Sources */,
@@ -3336,6 +3356,7 @@
3557F7B21E1D27D300CCA5E6 /* MGLDistanceFormatter.m in Sources */,
9C6E282B22A981570056B7BE /* MGLMapboxEvents.m in Sources */,
1FCCEC36222605C400302E3B /* MGLSDKMetricsManager.m in Sources */,
+ 96E6145B22CC169000109F14 /* MGLCompassButton.mm in Sources */,
40834BE71FE05E1800C1BD0D /* MMEAPIClient.m in Sources */,
DA8848591CBAFB9800AB86E3 /* MGLMapView.mm in Sources */,
DA8848501CBAFB9800AB86E3 /* MGLAnnotationImage.m in Sources */,
@@ -3462,6 +3483,7 @@
1FCCEC37222605C400302E3B /* MGLSDKMetricsManager.m in Sources */,
9C6E282C22A981580056B7BE /* MGLMapboxEvents.m in Sources */,
DAA4E41F1CBB730400178DFB /* MGLMultiPoint.mm in Sources */,
+ 96E6145C22CC169000109F14 /* MGLCompassButton.mm in Sources */,
DD0902AA1DB1929D00C5BDCE /* MGLNetworkConfiguration.m in Sources */,
40834C041FE05E1800C1BD0D /* MMELocationManager.m in Sources */,
9C6E281F22A980AC0056B7BE /* CLLocation+MMEMobileEvents.m in Sources */,
diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml
index d459e93613..f5d0040ea5 100644
--- a/platform/ios/jazzy.yml
+++ b/platform/ios/jazzy.yml
@@ -57,6 +57,7 @@ custom_categories:
children:
- MGLCalloutView
- MGLCalloutViewDelegate
+ - MGLCompassButton
- name: Location Updates
children:
- MGLLocationManager
diff --git a/platform/ios/sdk-files.json b/platform/ios/sdk-files.json
index fd2debd2e0..5ce2423ba9 100644
--- a/platform/ios/sdk-files.json
+++ b/platform/ios/sdk-files.json
@@ -64,6 +64,7 @@
"platform/darwin/src/MGLDistanceFormatter.m",
"platform/ios/src/MGLMapboxEvents.m",
"platform/darwin/src/MGLSDKMetricsManager.m",
+ "platform/ios/src/MGLCompassButton.mm",
"platform/ios/vendor/mapbox-events-ios/MapboxMobileEvents/MMEAPIClient.m",
"platform/ios/src/MGLMapView.mm",
"platform/ios/src/MGLAnnotationImage.m",
@@ -141,6 +142,7 @@
"MGLStyle.h": "platform/darwin/src/MGLStyle.h",
"MGLUserLocationAnnotationView.h": "platform/ios/src/MGLUserLocationAnnotationView.h",
"MGLAnnotation.h": "platform/darwin/src/MGLAnnotation.h",
+ "MGLCompassButton.h": "platform/ios/src/MGLCompassButton.h",
"MGLLight.h": "platform/darwin/src/MGLLight.h",
"MGLPointCollection.h": "platform/darwin/src/MGLPointCollection.h",
"MGLAnnotationView.h": "platform/ios/src/MGLAnnotationView.h",
@@ -195,6 +197,7 @@
"private_headers": {
"MGLFillStyleLayer_Private.h": "platform/darwin/src/MGLFillStyleLayer_Private.h",
"MGLOfflinePack_Private.h": "platform/darwin/src/MGLOfflinePack_Private.h",
+ "MGLCompassButton_Private.h": "platform/ios/src/MGLCompassButton_Private.h",
"MGLSource_Private.h": "platform/darwin/src/MGLSource_Private.h",
"MGLAnnotationView_Private.h": "platform/ios/src/MGLAnnotationView_Private.h",
"MGLValueEvaluator.h": "platform/darwin/src/MGLValueEvaluator.h",
diff --git a/platform/ios/src/MGLCompassButton.h b/platform/ios/src/MGLCompassButton.h
new file mode 100644
index 0000000000..9c3d9d1c77
--- /dev/null
+++ b/platform/ios/src/MGLCompassButton.h
@@ -0,0 +1,22 @@
+#import <UIKit/UIKit.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ A specialized view that displays the current compass heading for its associated map.
+ */
+MGL_EXPORT
+@interface MGLCompassButton : UIImageView
+
+/**
+ The visibility of the compass button.
+
+ You can configure a compass button to be visible all the time or only when the compass heading changes.
+ */
+@property (nonatomic, assign) MGLOrnamentVisibility compassVisibility;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLCompassButton.mm b/platform/ios/src/MGLCompassButton.mm
new file mode 100644
index 0000000000..94a6820a74
--- /dev/null
+++ b/platform/ios/src/MGLCompassButton.mm
@@ -0,0 +1,128 @@
+#import "MGLCompassButton_Private.h"
+#import "MGLCompassDirectionFormatter.h"
+
+#import <Mapbox/MGLGeometry.h>
+
+#import "MGLMapView_Private.h"
+#import "UIImage+MGLAdditions.h"
+#import "NSBundle+MGLAdditions.h"
+
+#include <mbgl/math/wrap.hpp>
+
+@interface MGLCompassButton ()
+
+@property (nonatomic, weak) MGLMapView *mapView;
+@property (nonatomic) MGLCompassDirectionFormatter *accessibilityCompassFormatter;
+
+@end
+
+@implementation MGLCompassButton
+
++ (instancetype)compassButtonWithMapView:(MGLMapView *)mapView {
+ return [[MGLCompassButton alloc] initWithMapView:mapView];
+}
+
+- (instancetype)initWithMapView:(MGLMapView *)mapView {
+ if (self = [super init]) {
+ self.mapView = mapView;
+ [self commonInit];
+ }
+ return self;
+}
+
+- (void)commonInit {
+ self.image = self.compassImage;
+
+ self.compassVisibility = MGLOrnamentVisibilityAdaptive;
+
+ self.alpha = 0;
+ self.userInteractionEnabled = YES;
+ self.translatesAutoresizingMaskIntoConstraints = NO;
+
+ UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
+ [self addGestureRecognizer:tapGesture];
+
+ self.accessibilityTraits = UIAccessibilityTraitButton;
+ self.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_LABEL", nil, nil, @"Compass", @"Accessibility label");
+ self.accessibilityHint = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_HINT", nil, nil, @"Rotates the map to face due north", @"Accessibility hint");
+
+ self.accessibilityCompassFormatter = [[MGLCompassDirectionFormatter alloc] init];
+ self.accessibilityCompassFormatter.unitStyle = NSFormattingUnitStyleLong;
+
+ [self sizeToFit];
+}
+
+- (void)setCompassVisibility:(MGLOrnamentVisibility)compassVisibility {
+ if (_compassVisibility == compassVisibility) { return; }
+ _compassVisibility = compassVisibility;
+
+ [self updateCompassAnimated:NO];
+}
+
+- (UIImage *)compassImage {
+ UIImage *scaleImage = [UIImage mgl_resourceImageNamed:@"Compass"];
+ UIGraphicsBeginImageContextWithOptions(scaleImage.size, NO, UIScreen.mainScreen.scale);
+ [scaleImage drawInRect:{CGPointZero, scaleImage.size}];
+
+ NSAttributedString *north = [[NSAttributedString alloc] initWithString:NSLocalizedStringWithDefaultValue(@"COMPASS_NORTH", nil, nil, @"N", @"Compass abbreviation for north") attributes:@{
+ NSFontAttributeName: [UIFont systemFontOfSize:11 weight:UIFontWeightUltraLight],
+ NSForegroundColorAttributeName: [UIColor whiteColor],
+ }];
+ CGRect stringRect = CGRectMake((scaleImage.size.width - north.size.width) / 2,
+ scaleImage.size.height * 0.435,
+ north.size.width, north.size.height);
+ [north drawInRect:stringRect];
+
+ UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ return image;
+}
+
+- (void)handleTapGesture:(__unused UITapGestureRecognizer *)sender {
+ [self.mapView resetNorth];
+}
+
+- (void)updateCompass {
+ [self updateCompassAnimated:YES];
+}
+
+- (void)updateCompassAnimated:(BOOL)animated {
+ CLLocationDirection direction = self.mapView.direction;
+ CLLocationDirection plateDirection = mbgl::util::wrap(-direction, 0., 360.);
+ self.transform = CGAffineTransformMakeRotation(MGLRadiansFromDegrees(plateDirection));
+
+ self.isAccessibilityElement = direction > 0;
+ self.accessibilityValue = [self.accessibilityCompassFormatter stringFromDirection:direction];
+
+ switch (self.compassVisibility) {
+ case MGLOrnamentVisibilityAdaptive:
+ if (direction > 0 && self.alpha < 1) {
+ [self showCompass:animated];
+ } else if (direction == 0 && self.alpha > 0) {
+ [self hideCompass:animated];
+ }
+ break;
+ case MGLOrnamentVisibilityVisible:
+ [self showCompass:animated];
+ break;
+ case MGLOrnamentVisibilityHidden:
+ [self hideCompass:animated];
+ break;
+ }
+}
+
+- (void)showCompass:(BOOL)animated {
+ animated ? [self animateToAlpha:1] : [self setAlpha:1];
+}
+
+- (void)hideCompass:(BOOL)animated {
+ animated ? [self animateToAlpha:0] : [self setAlpha:0];
+}
+
+- (void)animateToAlpha:(CGFloat)alpha {
+ [UIView animateWithDuration:MGLAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
+ self.alpha = alpha;
+ } completion:nil];
+}
+
+@end
diff --git a/platform/ios/src/MGLCompassButton_Private.h b/platform/ios/src/MGLCompassButton_Private.h
new file mode 100644
index 0000000000..c9741d79e3
--- /dev/null
+++ b/platform/ios/src/MGLCompassButton_Private.h
@@ -0,0 +1,19 @@
+#import <UIKit/UIKit.h>
+
+#import <Mapbox/MGLCompassButton.h>
+
+@class MGLMapView;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLCompassButton (Private)
+
++ (instancetype)compassButtonWithMapView:(MGLMapView *)mapView;
+
+@property (nonatomic, weak) MGLMapView *mapView;
+
+- (void)updateCompass;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h
index 87c24cd97b..9fe9a6c10a 100644
--- a/platform/ios/src/MGLMapView.h
+++ b/platform/ios/src/MGLMapView.h
@@ -1,9 +1,9 @@
-#import "MGLGeometry.h"
-#import "MGLMapCamera.h"
-
#import <UIKit/UIKit.h>
+#import "MGLCompassButton.h"
#import "MGLFoundation.h"
+#import "MGLGeometry.h"
+#import "MGLMapCamera.h"
#import "MGLTypes.h"
NS_ASSUME_NONNULL_BEGIN
@@ -122,7 +122,6 @@ FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapView
FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const MGLMissingLocationServicesUsageDescriptionException;
FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const MGLUserLocationAnnotationTypeException;
-FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const MGLResourceNotFoundException;
/**
An interactive, customizable map view with an interface similar to the one
@@ -316,7 +315,7 @@ MGL_EXPORT
A control indicating the map’s direction and allowing the user to manipulate
the direction, positioned in the upper-right corner.
*/
-@property (nonatomic, readonly) UIImageView *compassView;
+@property (nonatomic, readonly) MGLCompassButton *compassView;
/**
The position of the compass view. The default value is `MGLOrnamentPositionTopRight`.
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index c0b7380607..d027f616bd 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -55,11 +55,11 @@
#import "MGLUserLocation_Private.h"
#import "MGLAnnotationImage_Private.h"
#import "MGLAnnotationView_Private.h"
+#import "MGLCompassButton_Private.h"
#import "MGLScaleBar.h"
#import "MGLStyle_Private.h"
#import "MGLStyleLayer_Private.h"
#import "MGLMapboxEvents.h"
-#import "MMEConstants.h"
#import "MGLSDKUpdateChecker.h"
#import "MGLCompactCalloutView.h"
#import "MGLAnnotationContainerView.h"
@@ -68,6 +68,7 @@
#import "MGLMapAccessibilityElement.h"
#import "MGLLocationManager_Private.h"
#import "MGLLoggingConfiguration_Private.h"
+#import "MMEConstants.h"
#include <algorithm>
#include <cstdlib>
@@ -86,7 +87,6 @@ const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondMaxim
const MGLExceptionName MGLMissingLocationServicesUsageDescriptionException = @"MGLMissingLocationServicesUsageDescriptionException";
const MGLExceptionName MGLUserLocationAnnotationTypeException = @"MGLUserLocationAnnotationTypeException";
-const MGLExceptionName MGLResourceNotFoundException = @"MGLResourceNotFoundException";
const MGLExceptionName MGLUnderlyingMapUnavailableException = @"MGLUnderlyingMapUnavailableException";
const CGPoint MGLOrnamentDefaultPositionOffset = CGPointMake(8, 8);
@@ -197,7 +197,7 @@ public:
@property (nonatomic) NSMutableArray<NSLayoutConstraint *> *scaleBarConstraints;
@property (nonatomic, readwrite) MGLScaleBar *scaleBar;
-@property (nonatomic, readwrite) UIImageView *compassView;
+@property (nonatomic, readwrite) MGLCompassButton *compassView;
@property (nonatomic) NSMutableArray<NSLayoutConstraint *> *compassViewConstraints;
@property (nonatomic, readwrite) UIImageView *logoView;
@property (nonatomic) NSMutableArray<NSLayoutConstraint *> *logoViewConstraints;
@@ -308,7 +308,6 @@ public:
BOOL _delegateHasFillColorsForShapeAnnotations;
BOOL _delegateHasLineWidthsForShapeAnnotations;
- MGLCompassDirectionFormatter *_accessibilityCompassFormatter;
NSArray<id <MGLFeature>> *_visiblePlaceFeatures;
NSArray<id <MGLFeature>> *_visibleRoadFeatures;
NSMutableSet<MGLFeatureAccessibilityElement *> *_featureAccessibilityElements;
@@ -436,8 +435,6 @@ public:
// self.isAccessibilityElement = YES;
self.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"MAP_A11Y_LABEL", nil, nil, @"Map", @"Accessibility label");
self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction | UIAccessibilityTraitAdjustable;
- _accessibilityCompassFormatter = [[MGLCompassDirectionFormatter alloc] init];
- _accessibilityCompassFormatter.unitStyle = NSFormattingUnitStyleLong;
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = YES;
if (@available(iOS 11.0, *)) { self.accessibilityIgnoresInvertColors = YES; }
@@ -510,7 +507,7 @@ public:
// setup logo
//
- UIImage *logo = [MGLMapView resourceImageNamed:@"mapbox"];
+ UIImage *logo = [UIImage mgl_resourceImageNamed:@"mapbox"];
_logoView = [[UIImageView alloc] initWithImage:logo];
_logoView.accessibilityTraits = UIAccessibilityTraitStaticText;
_logoView.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"LOGO_A11Y_LABEL", nil, nil, @"Mapbox", @"Accessibility label");
@@ -538,14 +535,7 @@ public:
// setup compass
//
- _compassView = [[UIImageView alloc] initWithImage:self.compassImage];
- _compassView.alpha = 0;
- _compassView.userInteractionEnabled = YES;
- [_compassView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCompassTapGesture:)]];
- _compassView.accessibilityTraits = UIAccessibilityTraitButton;
- _compassView.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_LABEL", nil, nil, @"Compass", @"Accessibility label");
- _compassView.accessibilityHint = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_HINT", nil, nil, @"Rotates the map to face due north", @"Accessibility hint");
- _compassView.translatesAutoresizingMaskIntoConstraints = NO;
+ _compassView = [MGLCompassButton compassButtonWithMapView:self];
[self addSubview:_compassView];
_compassViewConstraints = [NSMutableArray array];
_compassViewPosition = MGLOrnamentPositionTopRight;
@@ -664,26 +654,6 @@ public:
static_cast<uint32_t>(size.height) };
}
-- (UIImage *)compassImage
-{
- UIImage *scaleImage = [MGLMapView resourceImageNamed:@"Compass"];
- UIGraphicsBeginImageContextWithOptions(scaleImage.size, NO, [UIScreen mainScreen].scale);
- [scaleImage drawInRect:{ CGPointZero, scaleImage.size }];
-
- NSAttributedString *north = [[NSAttributedString alloc] initWithString:NSLocalizedStringWithDefaultValue(@"COMPASS_NORTH", nil, nil, @"N", @"Compass abbreviation for north") attributes:@{
- NSFontAttributeName: [UIFont systemFontOfSize:11 weight:UIFontWeightUltraLight],
- NSForegroundColorAttributeName: [UIColor whiteColor],
- }];
- CGRect stringRect = CGRectMake((scaleImage.size.width - north.size.width) / 2,
- scaleImage.size.height * 0.435,
- north.size.width, north.size.height);
- [north drawInRect:stringRect];
-
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
-}
-
- (void)reachabilityChanged:(NSNotification *)notification
{
MGLAssertIsMainThread();
@@ -1522,19 +1492,6 @@ public:
#pragma mark - Gestures -
-- (void)handleCompassTapGesture:(__unused id)sender
-{
- self.cameraChangeReasonBitmask |= MGLCameraChangeReasonResetNorth;
-
- [self resetNorthAnimated:YES];
-
- if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading ||
- self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
- {
- self.userTrackingMode = MGLUserTrackingModeFollow;
- }
-}
-
- (void)touchesBegan:(__unused NSSet<UITouch *> *)touches withEvent:(__unused UIEvent *)event
{
if (!self.zoomEnabled && !self.pitchEnabled && !self.rotateEnabled && !self.scrollEnabled)
@@ -2547,6 +2504,8 @@ public:
- (void)resetNorthAnimated:(BOOL)animated
{
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonResetNorth;
+
[self setDirection:0 animated:animated];
}
@@ -3467,7 +3426,8 @@ public:
MGLLogDebug(@"Setting direction: %f animated: %@", direction, MGLStringFromBOOL(animated));
if ( ! animated && ! self.rotationAllowed) return;
- if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading)
+ if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading ||
+ self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
{
self.userTrackingMode = MGLUserTrackingModeFollow;
}
@@ -4177,7 +4137,7 @@ public:
/// rect therefore excludes the bottom half.
- (MGLAnnotationImage *)defaultAnnotationImage
{
- UIImage *image = [MGLMapView resourceImageNamed:MGLDefaultStyleMarkerSymbolName];
+ UIImage *image = [UIImage mgl_resourceImageNamed:MGLDefaultStyleMarkerSymbolName];
image = [image imageWithAlignmentRectInsets:
UIEdgeInsetsMake(0, 0, image.size.height / 2, 0)];
MGLAnnotationImage *annotationImage = [MGLAnnotationImage annotationImageWithImage:image
@@ -6547,35 +6507,7 @@ public:
- (void)updateCompass
{
- CLLocationDirection direction = self.direction;
- CLLocationDirection plateDirection = mbgl::util::wrap(-direction, 0., 360.);
- self.compassView.transform = CGAffineTransformMakeRotation(MGLRadiansFromDegrees(plateDirection));
-
- self.compassView.isAccessibilityElement = direction > 0;
- self.compassView.accessibilityValue = [_accessibilityCompassFormatter stringFromDirection:direction];
-
- if (direction > 0 && self.compassView.alpha < 1)
- {
- [UIView animateWithDuration:MGLAnimationDuration
- delay:0
- options:UIViewAnimationOptionBeginFromCurrentState
- animations:^
- {
- self.compassView.alpha = 1;
- }
- completion:nil];
- }
- else if (direction == 0 && self.compassView.alpha > 0)
- {
- [UIView animateWithDuration:MGLAnimationDuration
- delay:0
- options:UIViewAnimationOptionBeginFromCurrentState
- animations:^
- {
- self.compassView.alpha = 0;
- }
- completion:nil];
- }
+ [self.compassView updateCompass];
}
- (void)updateScaleBar
@@ -6593,21 +6525,6 @@ public:
}
}
-+ (UIImage *)resourceImageNamed:(NSString *)imageName
-{
- UIImage *image = [UIImage imageNamed:imageName
- inBundle:[NSBundle mgl_frameworkBundle]
- compatibleWithTraitCollection:nil];
-
- if ( ! image)
- {
- [NSException raise:MGLResourceNotFoundException format:
- @"The resource named “%@” could not be found in the Mapbox framework bundle.", imageName];
- }
-
- return image;
-}
-
- (BOOL)isFullyLoaded
{
return self.mbglMap.isFullyLoaded();
diff --git a/platform/ios/src/MGLMapView_Private.h b/platform/ios/src/MGLMapView_Private.h
index 5aa4902a91..e53dc8519c 100644
--- a/platform/ios/src/MGLMapView_Private.h
+++ b/platform/ios/src/MGLMapView_Private.h
@@ -12,6 +12,9 @@ namespace mbgl {
class MGLMapViewImpl;
@class MGLSource;
+/// Standard animation duration for UI elements.
+FOUNDATION_EXTERN const NSTimeInterval MGLAnimationDuration;
+
/// Minimum size of an annotation’s accessibility element.
FOUNDATION_EXTERN const CGSize MGLAnnotationAccessibilityElementMinimumSize;
diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h
index 635bda490f..98e673577c 100644
--- a/platform/ios/src/Mapbox.h
+++ b/platform/ios/src/Mapbox.h
@@ -15,6 +15,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[];
#import "MGLCalloutView.h"
#import "MGLClockDirectionFormatter.h"
#import "MGLCluster.h"
+#import "MGLCompassButton.h"
#import "MGLCompassDirectionFormatter.h"
#import "MGLCoordinateFormatter.h"
#import "MGLDistanceFormatter.h"
diff --git a/platform/ios/src/UIImage+MGLAdditions.h b/platform/ios/src/UIImage+MGLAdditions.h
index 22bb740242..55835c50d6 100644
--- a/platform/ios/src/UIImage+MGLAdditions.h
+++ b/platform/ios/src/UIImage+MGLAdditions.h
@@ -1,9 +1,13 @@
#import <UIKit/UIKit.h>
+#import <Mapbox/MGLTypes.h>
+
#include <mbgl/style/image.hpp>
NS_ASSUME_NONNULL_BEGIN
+FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const MGLResourceNotFoundException;
+
@interface UIImage (MGLAdditions)
- (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage;
@@ -14,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN
- (mbgl::PremultipliedImage)mgl_premultipliedImage;
++ (UIImage *)mgl_resourceImageNamed:(NSString *)imageName;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm
index 884f92e003..9d05abd6ca 100644
--- a/platform/ios/src/UIImage+MGLAdditions.mm
+++ b/platform/ios/src/UIImage+MGLAdditions.mm
@@ -1,7 +1,10 @@
#import "UIImage+MGLAdditions.h"
+#import "NSBundle+MGLAdditions.h"
#include <mbgl/util/image+MGLAdditions.hpp>
+const MGLExceptionName MGLResourceNotFoundException = @"MGLResourceNotFoundException";
+
@implementation UIImage (MGLAdditions)
- (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage
@@ -42,7 +45,20 @@
float(self.scale), isTemplate);
}
--(mbgl::PremultipliedImage)mgl_premultipliedImage {
+- (mbgl::PremultipliedImage)mgl_premultipliedImage {
return MGLPremultipliedImageFromCGImage(self.CGImage);
}
+
++ (UIImage *)mgl_resourceImageNamed:(NSString *)imageName {
+ UIImage *image = [UIImage imageNamed:imageName
+ inBundle:[NSBundle mgl_frameworkBundle]
+ compatibleWithTraitCollection:nil];
+
+ if (!image) {
+ [NSException raise:MGLResourceNotFoundException format:@"The resource named “%@” could not be found in the Mapbox framework bundle.", imageName];
+ }
+
+ return image;
+}
+
@end
diff --git a/platform/ios/test/MGLCompassButtonTests.m b/platform/ios/test/MGLCompassButtonTests.m
new file mode 100644
index 0000000000..f41de0a2e2
--- /dev/null
+++ b/platform/ios/test/MGLCompassButtonTests.m
@@ -0,0 +1,84 @@
+#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
+
+#import "../../ios/src/MGLCompassButton_Private.h"
+
+@interface MGLMapView (MGLCompassButtonTests)
+
+- (void)resetNorthAnimated:(BOOL)animated;
+
+@end
+
+@interface MGLCompassButtonTests : XCTestCase
+
+@property (nonatomic) MGLMapView *mapView;
+
+@end
+
+@implementation MGLCompassButtonTests
+
+- (void)setUp {
+ [super setUp];
+
+ [MGLAccountManager setAccessToken:@"pk.feedcafedeadbeefbadebede"];
+ NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
+ self.mapView = [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL];
+}
+
+- (void)tearDown {
+ self.mapView = nil;
+ [MGLAccountManager setAccessToken:nil];
+
+ [super tearDown];
+}
+
+- (void)testCompassButton {
+ XCTAssertNotNil(self.mapView.compassView);
+ XCTAssertTrue(self.mapView.compassView.userInteractionEnabled);
+ XCTAssertEqual(self.mapView.compassView.gestureRecognizers.count, 1);
+ XCTAssertEqual(self.mapView.compassView.accessibilityTraits, UIAccessibilityTraitButton);
+ XCTAssertNotNil(self.mapView.compassView.accessibilityLabel);
+ XCTAssertNotNil(self.mapView.compassView.accessibilityHint);
+}
+
+- (void)testVisibilityAdaptive {
+ XCTAssertEqual(self.mapView.compassView.compassVisibility, MGLOrnamentVisibilityAdaptive, @"Adaptive should be the default visibility setting.");
+ XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should not initially be visible.");
+
+ self.mapView.zoomLevel = 15.f;
+ [self.mapView setDirection:45.f animated:false];
+ XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001);
+ XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should become visible when direction changes.");
+
+ [self.mapView resetNorthAnimated:NO];
+ XCTAssertEqual(self.mapView.direction, 0);
+ XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should hide itself when direction is north.");
+}
+
+- (void)testVisibilityHidden {
+ self.mapView.compassView.compassVisibility = MGLOrnamentVisibilityHidden;
+ XCTAssertEqual(self.mapView.compassView.compassVisibility, MGLOrnamentVisibilityHidden);
+ XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should not initially be visible.");
+
+ self.mapView.zoomLevel = 15.f;
+ [self.mapView setDirection:45.f animated:false];
+ XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001);
+ XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should stay hidden when direction changes.");
+}
+
+- (void)testVisibilityVisible {
+ self.mapView.compassView.compassVisibility = MGLOrnamentVisibilityVisible;
+ XCTAssertEqual(self.mapView.compassView.compassVisibility, MGLOrnamentVisibilityVisible);
+ XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should initially be visible.");
+
+ self.mapView.zoomLevel = 15.f;
+ [self.mapView setDirection:45.f animated:false];
+ XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001);
+ XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should continue to be visible when direction changes.");
+
+ [self.mapView resetNorthAnimated:NO];
+ XCTAssertEqual(self.mapView.direction, 0);
+ XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should continue to be visible when direction is north.");
+}
+
+@end
diff --git a/platform/ios/test/MGLMapViewDirectionTests.mm b/platform/ios/test/MGLMapViewDirectionTests.mm
index 7f7e0903e3..d3990b581b 100644
--- a/platform/ios/test/MGLMapViewDirectionTests.mm
+++ b/platform/ios/test/MGLMapViewDirectionTests.mm
@@ -51,6 +51,39 @@ static MGLMapView *mapView;
XCTAssertEqual(mapView.direction, 0, @"Reset-to-north should set direction to 0°.");
}
+- (void)testRotateEnabled {
+ mapView.zoomLevel = 10;
+
+ UIRotationGestureRecognizer *gesture = [[UIRotationGestureRecognizer alloc] initWithTarget:nil action:nil];
+ gesture.state = UIGestureRecognizerStateBegan;
+ gesture.rotation = MGLRadiansFromDegrees(30);
+ CGFloat wrappedRotation = mbgl::util::wrap(-MGLDegreesFromRadians(gesture.rotation), 0., 360.);
+
+ // Disabled
+ {
+ mapView.rotateEnabled = NO;
+ XCTAssertEqual(mapView.allowsRotating, NO);
+
+ [mapView handleRotateGesture:gesture];
+ XCTAssertNotEqual(mapView.direction, wrappedRotation, @"Gestural rotation should not work when rotation is disabled.");
+
+ mapView.direction = 45.f;
+ XCTAssertEqualWithAccuracy(mapView.direction, 45, 0.001, @"Programmatic rotation is allowed when rotateEnabled = NO.");
+ }
+
+ // Enabled
+ {
+ [mapView resetNorthAnimated:NO];
+ mapView.rotateEnabled = YES;
+ XCTAssertEqual(mapView.allowsRotating, YES);
+
+ gesture.state = UIGestureRecognizerStateChanged;
+ gesture.rotation = MGLRadiansFromDegrees(30);
+ [mapView handleRotateGesture:gesture];
+ XCTAssertEqualWithAccuracy(mapView.direction, wrappedRotation, 0.001, @"Gestural rotation should work when rotation is enabled.");
+ }
+}
+
- (void)testRotateGesture {
mapView.zoomLevel = 15;