summaryrefslogtreecommitdiff
path: root/platform/ios
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios')
-rw-r--r--platform/ios/CHANGELOG.md16
-rw-r--r--platform/ios/app/Info.plist6
-rw-r--r--platform/ios/app/MBXViewController.m42
-rw-r--r--platform/ios/app/Main.storyboard32
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj44
-rw-r--r--platform/ios/resources/es.lproj/Localizable.strings22
-rw-r--r--platform/ios/resources/lt.lproj/Localizable.stringsdict12
-rw-r--r--platform/ios/resources/ru.lproj/Localizable.strings20
-rw-r--r--platform/ios/resources/sv.lproj/Localizable.strings6
-rw-r--r--platform/ios/resources/uk.lproj/Localizable.strings26
-rw-r--r--platform/ios/resources/uk.lproj/Localizable.stringsdict38
-rw-r--r--platform/ios/resources/zh-Hant.lproj/Localizable.strings39
-rw-r--r--platform/ios/resources/zh-Hant.lproj/Localizable.stringsdict30
-rwxr-xr-xplatform/ios/scripts/package.sh4
-rw-r--r--platform/ios/src/MGLFaux3DUserLocationAnnotationView.h10
-rw-r--r--platform/ios/src/MGLFaux3DUserLocationAnnotationView.m180
-rw-r--r--platform/ios/src/MGLMapView+IBAdditions.h1
-rw-r--r--platform/ios/src/MGLMapView.h46
-rw-r--r--platform/ios/src/MGLMapView.mm471
-rw-r--r--platform/ios/src/MGLUserLocation.h3
-rw-r--r--platform/ios/src/MGLUserLocation.m3
-rw-r--r--platform/ios/src/MGLUserLocationHeadingArrowLayer.h11
-rw-r--r--platform/ios/src/MGLUserLocationHeadingArrowLayer.m59
-rw-r--r--platform/ios/src/MGLUserLocationHeadingBeamLayer.h11
-rw-r--r--platform/ios/src/MGLUserLocationHeadingBeamLayer.m104
-rw-r--r--platform/ios/src/MGLUserLocationHeadingIndicator.h10
m---------platform/ios/uitest/KIF0
m---------platform/ios/uitest/OHHTTPStubs0
28 files changed, 904 insertions, 342 deletions
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 6d54e0a7e7..8dd1c5d8c7 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -34,7 +34,21 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* Fixed an issue that could cause line label rendering glitches when the line geometry is projected to a point behind the plane of the camera. ([#9865](https://github.com/mapbox/mapbox-gl-native/pull/9865))
* Fixed an issue that could cause a crash when using `-[MGLMapView flyToCamera:completionHandler:]` and related methods with zoom levels at or near the maximum value. ([#9381](https://github.com/mapbox/mapbox-gl-native/pull/9381))
-## 3.6.2
+## 3.6.4 - September 25, 2017
+
+* Fixed an issue where stale (but still valid) map data could be ignored in offline mode. ([#10012](https://github.com/mapbox/mapbox-gl-native/pull/10012))
+
+## 3.6.3 - September 15, 2017
+
+* Added the option to display an always-on heading indicator with the default user location annotation, controlled via the `MGLMapView.showsUserHeadingIndicator` property. ([#9886](https://github.com/mapbox/mapbox-gl-native/pull/9886))
+* Fixed an issue where user heading tracking mode would update too frequently. ([#9845](https://github.com/mapbox/mapbox-gl-native/pull/9845))
+* Added support for iOS 11 location usage descriptions. ([#9869](https://github.com/mapbox/mapbox-gl-native/pull/9869))
+* Fixed an issue where `MGLUserLocation.location` did not follow its documented initialization behavior. This property will now properly return `nil` until the user’s location has been determined. ([#9639](https://github.com/mapbox/mapbox-gl-native/pull/9639))
+* `MGLMapView`’s `minimumZoomLevel` and `maximumZoomLevel` properties are now available in Interface Builder’s Attributes inspector. ([#9729](https://github.com/mapbox/mapbox-gl-native/pull/9729))
+* Deprecated `+[MGLStyle trafficDayStyleURL]` and `+[MGLStyle trafficNightStyleURL]` with no replacement method. To use the Traffic Day and Traffic Night styles going forward, we recommend that you use the underlying URL. ([#9918](https://github.com/mapbox/mapbox-gl-native/pull/9918))
+* Fixed a crash that sometimes occurred when a map view's view controller was deallocated. ([#9995](https://github.com/mapbox/mapbox-gl-native/pull/9995))
+
+## 3.6.2 - August 18, 2017
* Added an `MGLStyle.localizesLabels` property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582))
* Added an additional camera method to MGLMapView that accepts an edge padding parameter. ([#9651](https://github.com/mapbox/mapbox-gl-native/pull/9651))
diff --git a/platform/ios/app/Info.plist b/platform/ios/app/Info.plist
index 167e66fa09..a98a8c10c5 100644
--- a/platform/ios/app/Info.plist
+++ b/platform/ios/app/Info.plist
@@ -27,9 +27,11 @@
<key>NSHumanReadableCopyright</key>
<string>© 2014–2017 Mapbox</string>
<key>NSLocationAlwaysUsageDescription</key>
- <string>The map will ALWAYS display the user’s location.</string>
+ <string>The map will display your location. The map may also use your location when it isn’t visible in order to improve OpenStreetMap and Mapbox products.</string>
<key>NSLocationWhenInUseUsageDescription</key>
- <string>The map will display the user’s location.</string>
+ <string>The map will display your location.</string>
+ <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
+ <string>The map will display your location. If you choose Always, the map may also use your location when it isn’t visible in order to improve OpenStreetMap and Mapbox products.</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m
index 8f7896ffee..07838bc6bd 100644
--- a/platform/ios/app/MBXViewController.m
+++ b/platform/ios/app/MBXViewController.m
@@ -115,7 +115,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
@property (nonatomic) IBOutlet MGLMapView *mapView;
-@property (weak, nonatomic) IBOutlet UILabel *hudLabel;
+@property (weak, nonatomic) IBOutlet UIButton *hudLabel;
@property (nonatomic) NSInteger styleIndex;
@property (nonatomic) BOOL debugLoggingEnabled;
@property (nonatomic) BOOL customUserLocationAnnnotationEnabled;
@@ -163,7 +163,9 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
self.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsDebugLoggingEnabled"];
self.mapView.scaleBar.hidden = NO;
+ self.mapView.showsUserHeadingIndicator = YES;
self.hudLabel.hidden = YES;
+ self.hudLabel.titleLabel.font = [UIFont monospacedDigitSystemFontOfSize:10 weight:UIFontWeightRegular];
if ([MGLAccountManager accessToken].length)
{
@@ -355,7 +357,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
[settingsTitles addObjectsFromArray:@[
[NSString stringWithFormat:@"%@ Reuse Queue Stats", (_reuseQueueStatsEnabled ? @"Hide" :@"Show")],
@"Start World Tour",
- [NSString stringWithFormat:@"%@ Zoom Level", (_showZoomLevelEnabled ? @"Hide" :@"Show")],
+ [NSString stringWithFormat:@"%@ Zoom/Pitch/Direction Label", (_showZoomLevelEnabled ? @"Hide" :@"Show")],
@"Embedded Map View",
[NSString stringWithFormat:@"%@ Second Map", ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")],
[NSString stringWithFormat:@"Show Labels in %@", (_usingLocaleBasedCountryLabels ? @"Default Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])],
@@ -550,6 +552,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
self.reuseQueueStatsEnabled = !self.reuseQueueStatsEnabled;
self.hudLabel.hidden = !self.reuseQueueStatsEnabled;
self.showZoomLevelEnabled = NO;
+ [self updateHUD];
break;
}
case MBXSettingsMiscellaneousShowZoomLevel:
@@ -557,6 +560,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
self.showZoomLevelEnabled = !self.showZoomLevelEnabled;
self.hudLabel.hidden = !self.showZoomLevelEnabled;
self.reuseQueueStatsEnabled = NO;
+ [self updateHUD];
break;
}
case MBXSettingsMiscellaneousScrollView:
@@ -1597,8 +1601,9 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
[MGLStyle darkStyleURL],
[MGLStyle satelliteStyleURL],
[MGLStyle satelliteStreetsStyleURL],
- [MGLStyle trafficDayStyleURL],
- [MGLStyle trafficNightStyleURL],
+ [NSURL URLWithString:@"mapbox://styles/mapbox/traffic-day-v2"],
+ [NSURL URLWithString:@"mapbox://styles/mapbox/traffic-night-v2"],
+
];
NSAssert(styleNames.count == styleURLs.count, @"Style names and URLs don’t match.");
@@ -1873,22 +1878,33 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
- (void)mapViewRegionIsChanging:(MGLMapView *)mapView
{
+ [self updateHUD];
+}
+
+- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
+ [self updateHUD];
+}
+
+- (void)mapView:(MGLMapView *)mapView didUpdateUserLocation:(MGLUserLocation *)userLocation {
+ [self updateHUD];
+}
+
+- (void)updateHUD {
+ if (!self.reuseQueueStatsEnabled && !self.showZoomLevelEnabled) return;
+
+ NSString *hudString;
+
if (self.reuseQueueStatsEnabled) {
NSUInteger queuedAnnotations = 0;
- for (NSArray *queue in self.mapView.annotationViewReuseQueueByIdentifier.allValues)
- {
+ for (NSArray *queue in self.mapView.annotationViewReuseQueueByIdentifier.allValues) {
queuedAnnotations += queue.count;
}
- self.hudLabel.text = [NSString stringWithFormat:@" Visible: %ld Queued: %ld", (unsigned long)mapView.visibleAnnotations.count, (unsigned long)queuedAnnotations];
+ hudString = [NSString stringWithFormat:@"Visible: %ld Queued: %ld", (unsigned long)self.mapView.visibleAnnotations.count, (unsigned long)queuedAnnotations];
} else if (self.showZoomLevelEnabled) {
- self.hudLabel.text = [NSString stringWithFormat:@" Zoom: %.2f", self.mapView.zoomLevel];
+ hudString = [NSString stringWithFormat:@"%.2f ∕ ↕\U0000FE0E%.f° ∕ %.f°", self.mapView.zoomLevel, self.mapView.camera.pitch, self.mapView.direction];
}
-}
-- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
- if (self.showZoomLevelEnabled) {
- self.hudLabel.text = [NSString stringWithFormat:@" Zoom: %.2f", self.mapView.zoomLevel];
- }
+ [self.hudLabel setTitle:hudString forState:UIControlStateNormal];
}
@end
diff --git a/platform/ios/app/Main.storyboard b/platform/ios/app/Main.storyboard
index c7bcd6e0f0..507582213f 100644
--- a/platform/ios/app/Main.storyboard
+++ b/platform/ios/app/Main.storyboard
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="PSe-Ot-7Ff">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G8c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="PSe-Ot-7Ff">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
@@ -33,17 +33,24 @@
<outletCollection property="gestureRecognizers" destination="lfd-mn-7en" appends="YES" id="0PH-gH-GRm"/>
</connections>
</view>
- <label opaque="NO" userInteractionEnabled="NO" alpha="0.69999999999999996" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="58y-pX-YyB">
- <rect key="frame" x="179" y="626" width="180" height="21"/>
+ <button opaque="NO" userInteractionEnabled="NO" alpha="0.69999999999999996" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="58y-pX-YyB">
+ <rect key="frame" x="319" y="606" width="40" height="21"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+ <accessibility key="accessibilityConfiguration">
+ <accessibilityTraits key="traits" button="YES" notEnabled="YES"/>
+ </accessibility>
<constraints>
- <constraint firstAttribute="width" constant="180" id="OL2-l5-I2f"/>
- <constraint firstAttribute="height" constant="21" id="xHg-ye-wzT"/>
+ <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="40" id="OL2-l5-I2f"/>
+ <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="xHg-ye-wzT"/>
</constraints>
- <fontDescription key="fontDescription" type="system" pointSize="8"/>
- <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
- <nil key="highlightedColor"/>
- </label>
+ <fontDescription key="fontDescription" type="system" pointSize="10"/>
+ <inset key="contentEdgeInsets" minX="4" minY="2" maxX="4" maxY="2"/>
+ <userDefinedRuntimeAttributes>
+ <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
+ <integer key="value" value="2"/>
+ </userDefinedRuntimeAttribute>
+ </userDefinedRuntimeAttributes>
+ </button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
@@ -51,7 +58,8 @@
<constraint firstItem="kNe-zV-9ha" firstAttribute="bottom" secondItem="m8o-i7-QIy" secondAttribute="top" id="Etp-BC-E1N"/>
<constraint firstAttribute="trailing" secondItem="kNe-zV-9ha" secondAttribute="trailing" id="MGr-8G-VEb"/>
<constraint firstItem="58y-pX-YyB" firstAttribute="trailing" secondItem="Z9X-fc-PUC" secondAttribute="trailingMargin" id="O3a-bR-boI"/>
- <constraint firstItem="m8o-i7-QIy" firstAttribute="top" secondItem="58y-pX-YyB" secondAttribute="bottom" constant="20" id="cjh-ZS-Mv4"/>
+ <constraint firstItem="58y-pX-YyB" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Z9X-fc-PUC" secondAttribute="leadingMargin" id="ceH-yz-ewY"/>
+ <constraint firstItem="m8o-i7-QIy" firstAttribute="top" secondItem="58y-pX-YyB" secondAttribute="bottom" constant="40" id="cjh-ZS-Mv4"/>
<constraint firstItem="kNe-zV-9ha" firstAttribute="top" secondItem="Z9X-fc-PUC" secondAttribute="top" id="qMm-e9-jxH"/>
</constraints>
</view>
@@ -66,7 +74,7 @@
</connections>
</barButtonItem>
<button key="titleView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="KsN-ny-Hou">
- <rect key="frame" x="61" y="7" width="207" height="30"/>
+ <rect key="frame" x="65" y="5.5" width="203" height="33"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<state key="normal" title="Streets"/>
@@ -94,7 +102,7 @@
</rightBarButtonItems>
</navigationItem>
<connections>
- <outlet property="hudLabel" destination="58y-pX-YyB" id="MEh-ir-3IH"/>
+ <outlet property="hudLabel" destination="58y-pX-YyB" id="aGG-7a-bZR"/>
<outlet property="mapView" destination="kNe-zV-9ha" id="VNR-WO-1q4"/>
<segue destination="zvf-Qd-4Ru" kind="show" identifier="ShowSnapshots" id="hzX-Jp-UJq"/>
</connections>
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index 060fb45d81..debbf1ccf3 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -229,8 +229,15 @@
9620BB391E69FE1700705A1D /* MGLSDKUpdateChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = 9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */; };
9620BB3A1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */; };
9620BB3B1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */; };
+ 966FCF4C1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 966FCF4A1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h */; };
+ 966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */; };
+ 966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */; };
+ 966FCF531F3C322400F2B6DE /* MGLUserLocationHeadingArrowLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 966FCF501F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.h */; };
+ 966FCF541F3C323300F2B6DE /* MGLUserLocationHeadingArrowLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */; };
+ 966FCF551F3C323500F2B6DE /* MGLUserLocationHeadingArrowLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */; };
968F36B51E4D128D003A5522 /* MGLDistanceFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3557F7AE1E1D27D300CCA5E6 /* MGLDistanceFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
96E027231E57C76E004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027251E57C76E004B8E66 /* Localizable.strings */; };
+ 96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */; };
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 */; };
@@ -707,6 +714,10 @@
9660916D1E5BBFDB00A9A03B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
9660916E1E5BBFDC00A9A03B /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
9660916F1E5BBFDE00A9A03B /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = "<group>"; };
+ 966FCF4A1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingBeamLayer.h; sourceTree = "<group>"; };
+ 966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationHeadingBeamLayer.m; sourceTree = "<group>"; };
+ 966FCF501F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingArrowLayer.h; sourceTree = "<group>"; };
+ 966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationHeadingArrowLayer.m; sourceTree = "<group>"; };
968F36B41E4D0FC6003A5522 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
96E027241E57C76E004B8E66 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
96E027271E57C77A004B8E66 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -717,6 +728,7 @@
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>"; };
+ 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.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>"; };
@@ -780,7 +792,7 @@
DA618B191E68883700CB7F44 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ca; path = ca.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
DA618B1A1E68883900CB7F44 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = "<group>"; };
DA618B1B1E68884E00CB7F44 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = "<group>"; };
- DA618B1C1E6888EC00CB7F44 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Foundation.strings; sourceTree = "<group>"; };
+ DA618B1C1E6888EC00CB7F44 /* ca */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Foundation.strings; sourceTree = "<group>"; };
DA618B1D1E6888F500CB7F44 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ca; path = ca.lproj/Foundation.stringsdict; sourceTree = "<group>"; };
DA618B1E1E688A3700CB7F44 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Root.strings; sourceTree = "<group>"; };
DA618B251E68920500CB7F44 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Foundation.strings; sourceTree = "<group>"; };
@@ -789,6 +801,10 @@
DA618B2C1E68933600CB7F44 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Root.strings; sourceTree = "<group>"; };
DA6408D91DA4E7D300908C90 /* MGLVectorStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLVectorStyleLayer.h; sourceTree = "<group>"; };
DA6408DA1DA4E7D300908C90 /* MGLVectorStyleLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLVectorStyleLayer.m; sourceTree = "<group>"; };
+ DA704CBB1F637311004B3F28 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Foundation.strings; sourceTree = "<group>"; };
+ DA704CBC1F637405004B3F28 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
+ DA704CBD1F63746E004B3F28 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
+ DA704CC71F6663A3004B3F28 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Foundation.strings; sourceTree = "<group>"; };
DA7262091DEEE3480043BB89 /* MGLOpenGLStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOpenGLStyleLayer.h; sourceTree = "<group>"; };
DA72620A1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLOpenGLStyleLayer.mm; sourceTree = "<group>"; };
DA737ADA1E59139D00AD2CDE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Foundation.stringsdict; sourceTree = "<group>"; };
@@ -904,7 +920,7 @@
DAA32CAC1E4C4971006F8D24 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
DAA32CB11E4C4C8A006F8D24 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Root.strings; sourceTree = "<group>"; };
DAA32CB51E4C4CF4006F8D24 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Foundation.strings; sourceTree = "<group>"; };
- DAA32CB71E4C4ED8006F8D24 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
+ DAA32CB71E4C4ED8006F8D24 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
DAA32CB81E4C4EE6006F8D24 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Root.strings; sourceTree = "<group>"; };
DAA32CBC1E4C4F5D006F8D24 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; };
DAA32CBD1E4C4F62006F8D24 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Foundation.strings; sourceTree = "<group>"; };
@@ -929,6 +945,7 @@
DABCABC01CB80717000A7C39 /* locations.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = locations.hpp; sourceTree = "<group>"; };
DAC49C621CD07D74009E1AA3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
DACCD9C81F1F473700BB09A1 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Root.strings; sourceTree = "<group>"; };
+ DACFE7981F66EA2100630DA8 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Foundation.stringsdict; sourceTree = "<group>"; };
DAD165691CF41981001FF4B9 /* MGLFeature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature.h; sourceTree = "<group>"; };
DAD1656A1CF41981001FF4B9 /* MGLFeature_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature_Private.h; sourceTree = "<group>"; };
DAD1656B1CF41981001FF4B9 /* MGLFeature.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFeature.mm; sourceTree = "<group>"; };
@@ -944,8 +961,8 @@
DAF0D80F1DFE0EA000B28378 /* MGLRasterSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLRasterSource_Private.h; sourceTree = "<group>"; };
DAF0D8121DFE0EC500B28378 /* MGLVectorSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLVectorSource_Private.h; sourceTree = "<group>"; };
DAF0D8171DFE6B2800B28378 /* MGLAttributionInfo_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo_Private.h; sourceTree = "<group>"; };
- DAFBD0D21E3FA7A1000CD6BF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Foundation.strings"; sourceTree = "<group>"; };
- DAFBD0D31E3FA7A1000CD6BF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
+ DAFBD0D21E3FA7A1000CD6BF /* zh-Hant */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Foundation.strings"; sourceTree = "<group>"; };
+ DAFBD0D31E3FA7A1000CD6BF /* zh-Hant */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
DAFBD0D41E3FA7A2000CD6BF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Root.strings"; sourceTree = "<group>"; };
DD0902A21DB18DE700C5BDCE /* MGLNetworkConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfiguration.m; sourceTree = "<group>"; };
DD0902A41DB18F1B00C5BDCE /* MGLNetworkConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLNetworkConfiguration.h; sourceTree = "<group>"; };
@@ -1619,8 +1636,8 @@
DAD165841CF4D06B001FF4B9 /* Annotations */ = {
isa = PBXGroup;
children = (
- 40EDA1BD1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.h */,
404326881D5B9B1A007111BD /* MGLAnnotationContainerView_Private.h */,
+ 40EDA1BD1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.h */,
40EDA1BE1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.m */,
DA8848401CBAFB9800AB86E3 /* MGLAnnotationImage_Private.h */,
DA8848341CBAFB8500AB86E3 /* MGLAnnotationImage.h */,
@@ -1639,6 +1656,11 @@
359F57451D2FDBD5005217F1 /* MGLUserLocationAnnotationView_Private.h */,
354B83941D2E873E005D9406 /* MGLUserLocationAnnotationView.h */,
354B83951D2E873E005D9406 /* MGLUserLocationAnnotationView.m */,
+ 966FCF501F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.h */,
+ 966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */,
+ 966FCF4A1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h */,
+ 966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */,
+ 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */,
);
name = Annotations;
sourceTree = "<group>";
@@ -1695,6 +1717,7 @@
DA8847FB1CBAFA5100AB86E3 /* MGLShape.h in Headers */,
353933F51D3FB785003F57D7 /* MGLBackgroundStyleLayer.h in Headers */,
DA88485A1CBAFB9800AB86E3 /* MGLUserLocation_Private.h in Headers */,
+ 966FCF531F3C322400F2B6DE /* MGLUserLocationHeadingArrowLayer.h in Headers */,
DA27C24F1CBB4C11000B0ECD /* MGLAccountManager_Private.h in Headers */,
DA8847FC1CBAFA5100AB86E3 /* MGLStyle.h in Headers */,
DD9BE4F71EB263C50079A3AF /* UIViewController+MGLAdditions.h in Headers */,
@@ -1726,9 +1749,11 @@
DA8847F41CBAFA5100AB86E3 /* MGLOfflinePack.h in Headers */,
DA88482E1CBAFA6200AB86E3 /* NSException+MGLAdditions.h in Headers */,
DA8848551CBAFB9800AB86E3 /* MGLLocationManager.h in Headers */,
+ 96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */,
408AA8571DAEDA1700022900 /* NSDictionary+MGLAdditions.h in Headers */,
DA88483F1CBAFB8500AB86E3 /* MGLUserLocation.h in Headers */,
558DE7A01E5615E400C7916D /* MGLFoundation_Private.h in Headers */,
+ 966FCF4C1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.h in Headers */,
DA88483D1CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h in Headers */,
DA17BE301CC4BAC300402C41 /* MGLMapView_Private.h in Headers */,
DAD165781CF4CDFF001FF4B9 /* MGLShapeCollection.h in Headers */,
@@ -2265,6 +2290,7 @@
3538AA1F1D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */,
DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */,
DA88482D1CBAFA6200AB86E3 /* NSBundle+MGLAdditions.m in Sources */,
+ 966FCF541F3C323300F2B6DE /* MGLUserLocationHeadingArrowLayer.m in Sources */,
DA88485B1CBAFB9800AB86E3 /* MGLUserLocation.m in Sources */,
927FBD011F4DB05500F8BF1F /* MGLMapSnapshotter.mm in Sources */,
350098BD1D480108004B2AF0 /* MGLVectorSource.mm in Sources */,
@@ -2305,6 +2331,7 @@
35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */,
7E016D861D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */,
DA8848521CBAFB9800AB86E3 /* MGLAPIClient.m in Sources */,
+ 966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */,
DA8848301CBAFA6200AB86E3 /* NSProcessInfo+MGLAdditions.m in Sources */,
353AFA161D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */,
35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */,
@@ -2349,6 +2376,7 @@
3538AA201D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */,
DA00FC911D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */,
DAA4E4201CBB730400178DFB /* MGLOfflinePack.mm in Sources */,
+ 966FCF551F3C323500F2B6DE /* MGLUserLocationHeadingArrowLayer.m in Sources */,
DAA4E4331CBB730400178DFB /* MGLUserLocation.m in Sources */,
927FBD021F4DB05500F8BF1F /* MGLMapSnapshotter.mm in Sources */,
350098BE1D480108004B2AF0 /* MGLVectorSource.mm in Sources */,
@@ -2390,6 +2418,7 @@
35B82BFB1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */,
7E016D871D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */,
DAA4E4311CBB730400178DFB /* MGLMapboxEvents.m in Sources */,
+ 966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */,
DAA4E4231CBB730400178DFB /* MGLPolygon.mm in Sources */,
353AFA171D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */,
35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */,
@@ -2538,6 +2567,8 @@
DA618B1C1E6888EC00CB7F44 /* ca */,
DA618B251E68920500CB7F44 /* lt */,
DAE9E0F11EB7BF1B001E8E8B /* es */,
+ DA704CBB1F637311004B3F28 /* ru */,
+ DA704CC71F6663A3004B3F28 /* uk */,
);
name = Foundation.strings;
sourceTree = "<group>";
@@ -2556,6 +2587,7 @@
DA1AC0201E5B8917006DF1D6 /* uk */,
DA618B1D1E6888F500CB7F44 /* ca */,
DA618B261E68920D00CB7F44 /* lt */,
+ DACFE7981F66EA2100630DA8 /* vi */,
);
name = Foundation.stringsdict;
sourceTree = "<group>";
@@ -2581,6 +2613,8 @@
DA57D4AA1EBA8ED300793288 /* es */,
DA57D4AB1EBA909900793288 /* lt */,
DA57D4AC1EBA922A00793288 /* vi */,
+ DA704CBC1F637405004B3F28 /* uk */,
+ DA704CBD1F63746E004B3F28 /* zh-Hant */,
);
name = Localizable.stringsdict;
sourceTree = "<group>";
diff --git a/platform/ios/resources/es.lproj/Localizable.strings b/platform/ios/resources/es.lproj/Localizable.strings
index 6fbfb23dda..75f799fcd9 100644
--- a/platform/ios/resources/es.lproj/Localizable.strings
+++ b/platform/ios/resources/es.lproj/Localizable.strings
@@ -1,4 +1,4 @@
-/* Accessibility hint */
+/* Accessibility hint */
"ANNOTATION_A11Y_HINT" = "Muestra más información";
/* No comment provided by engineer. */
@@ -10,6 +10,9 @@
/* No comment provided by engineer. */
"CANCEL" = "Cancelar";
+/* Accessibility hint for closing the selected annotation’s callout view and returning to the map */
+"CLOSE_CALLOUT_A11Y_HINT" = "Regresa al mapa";
+
/* Accessibility hint */
"COMPASS_A11Y_HINT" = "Gira el mapa para hacer frente al norte";
@@ -31,6 +34,12 @@
/* Accessibility label */
"INFO_A11Y_LABEL" = "Acerca de este mapa";
+/* User-friendly error description */
+"LOAD_MAP_FAILED_DESC" = "No se pudo cargar el mapa debido a un error desconocido.";
+
+/* User-friendly error description */
+"LOAD_STYLE_FAILED_DESC" = "No se pudo cargar el mapa debido a un error de carga en el estilo.";
+
/* Accessibility label */
"LOGO_A11Y_LABEL" = "Mapbox";
@@ -38,11 +47,20 @@
"MAP_A11Y_LABEL" = "Mapa";
/* Map accessibility value */
-"MAP_A11Y_VALUE" = "Zoom %1$dx\nAnotaciones visibles: %2$ld";
+"MAP_A11Y_VALUE" = "Zoom x%1$d\nAnotaciones visibles: %2$ld";
+
+/* User-friendly error description */
+"PARSE_STYLE_FAILED_DESC" = "No se pudo cargar el mapa debido a que el estilo está dañado.";
/* Action sheet title */
"SDK_NAME" = "Mapbox iOS SDK";
+/* Developer-only SDK update notification; {latest version, in format x.x.x} */
+"SDK_UPDATE_AVAILABLE" = "La versión %@ de Mapbox iOS SDK está disponible:";
+
+/* User-friendly error description */
+"STYLE_NOT_FOUND_DESC" = "No se pudo cargar el mapa debido a que no se encuentra el estilo o está incompleto.";
+
/* Telemetry prompt message */
"TELEMETRY_DISABLED_MSG" = "Ayudas a mejorar los mapas de OpenStreetMap y Mapbox al aportar datos de uso anónimos.";
diff --git a/platform/ios/resources/lt.lproj/Localizable.stringsdict b/platform/ios/resources/lt.lproj/Localizable.stringsdict
index 0200327f04..732a8d23ac 100644
--- a/platform/ios/resources/lt.lproj/Localizable.stringsdict
+++ b/platform/ios/resources/lt.lproj/Localizable.stringsdict
@@ -14,11 +14,11 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
- <string>Priartinimas: %dx</string>
+ <string>Priartinimas %dx</string>
<key>few</key>
- <string>Priartinimas: %dx</string>
+ <string>Priartinimas %dx</string>
<key>other</key>
- <string>Priartinimas: %dx</string>
+ <string>Priartinimas %dx</string>
</dict>
<key>count</key>
<dict>
@@ -27,11 +27,11 @@
<key>NSStringFormatValueTypeKey</key>
<string>ld</string>
<key>one</key>
- <string>Matomos anotacijos: %d anotacija</string>
+ <string>%d matoma anotacija</string>
<key>few</key>
- <string>Matomos anotacijos: %d anotacijos</string>
+ <string>%d matomos anotacijos</string>
<key>other</key>
- <string>Matomos anotacijos: %d anotacijų</string>
+ <string>%d matomų anotacijų</string>
</dict>
</dict>
</dict>
diff --git a/platform/ios/resources/ru.lproj/Localizable.strings b/platform/ios/resources/ru.lproj/Localizable.strings
index 1c3b46f057..40f59a3356 100644
--- a/platform/ios/resources/ru.lproj/Localizable.strings
+++ b/platform/ios/resources/ru.lproj/Localizable.strings
@@ -2,7 +2,7 @@
"ANNOTATION_A11Y_HINT" = "Показать больше информации";
/* No comment provided by engineer. */
-"API_CLIENT_400_DESC" = "The session data task failed. Original request was: %@";
+"API_CLIENT_400_DESC" = "The session data task failed. Original request was:%@";
/* No comment provided by engineer. */
"API_CLIENT_400_REASON" = "The status code was %ld";
@@ -10,6 +10,9 @@
/* No comment provided by engineer. */
"CANCEL" = "Отмена";
+/* Accessibility hint for closing the selected annotation’s callout view and returning to the map */
+"CLOSE_CALLOUT_A11Y_HINT" = "Вернуться на карту";
+
/* Accessibility hint */
"COMPASS_A11Y_HINT" = "Повернуть карту на север";
@@ -31,6 +34,12 @@
/* Accessibility label */
"INFO_A11Y_LABEL" = "Об этой карте";
+/* User-friendly error description */
+"LOAD_MAP_FAILED_DESC" = "Не удалось загрузить карту из-за неизвестной ошибки.";
+
+/* User-friendly error description */
+"LOAD_STYLE_FAILED_DESC" = "Не удалось загрузить карту так как невозможно загрузить стиль.";
+
/* Accessibility label */
"LOGO_A11Y_LABEL" = "Mapbox";
@@ -40,9 +49,18 @@
/* Map accessibility value */
"MAP_A11Y_VALUE" = "Масштаб %1$dx\n%2$ld аннотации(й) видны";
+/* User-friendly error description */
+"PARSE_STYLE_FAILED_DESC" = "Не удалось загрузить карту из-за ошибки в стиле.";
+
/* Action sheet title */
"SDK_NAME" = "Mapbox iOS SDK";
+/* Developer-only SDK update notification; {latest version, in format x.x.x} */
+"SDK_UPDATE_AVAILABLE" = "Mapbox iOS SDK версии %@теперь доступен.";
+
+/* User-friendly error description */
+"STYLE_NOT_FOUND_DESC" = "Не удалось загрузить карту так как стиль не найден или несовместим.";
+
/* Telemetry prompt message */
"TELEMETRY_DISABLED_MSG" = "Вы можете помочь сделать карты OpenStreetMap и Mapbox лучше путем предоставления анонимных данных об использовании.";
diff --git a/platform/ios/resources/sv.lproj/Localizable.strings b/platform/ios/resources/sv.lproj/Localizable.strings
index fb787b973a..b10b1b06ed 100644
--- a/platform/ios/resources/sv.lproj/Localizable.strings
+++ b/platform/ios/resources/sv.lproj/Localizable.strings
@@ -38,7 +38,7 @@
"LOAD_MAP_FAILED_DESC" = "Kartan kunde inte laddas på grund av att ett okänt fel inträffade.";
/* User-friendly error description */
-"LOAD_STYLE_FAILED_DESC" = "The map failed to load because the style can't be loaded.";
+"LOAD_STYLE_FAILED_DESC" = "Kartan kunde inte laddas på grund av att stilen är skadad.";
/* Accessibility label */
"LOGO_A11Y_LABEL" = "Mapbox";
@@ -50,7 +50,7 @@
"MAP_A11Y_VALUE" = "Zoom %1$dx\n%2$ld annotering(ar) synliga";
/* User-friendly error description */
-"PARSE_STYLE_FAILED_DESC" = "The map failed to load because the style is corrupted.";
+"PARSE_STYLE_FAILED_DESC" = "Kartan kunde inte laddas på grund av att stilen är skadad.";
/* Action sheet title */
"SDK_NAME" = "Mapbox iOS SDK";
@@ -59,7 +59,7 @@
"SDK_UPDATE_AVAILABLE" = "Mapbox iOS SDK version %@ är nu tillgängligt:";
/* User-friendly error description */
-"STYLE_NOT_FOUND_DESC" = "The map failed to load because the style can’t be found or is incompatible.";
+"STYLE_NOT_FOUND_DESC" = "Kartan kunde inte laddas på grund av ett okänt fel.";
/* Telemetry prompt message */
"TELEMETRY_DISABLED_MSG" = "Du kan hjälpa till att göra OpenStreetMap och Mapbox karttjänster bättre genom att bidra med anonymiserad användningsdata.";
diff --git a/platform/ios/resources/uk.lproj/Localizable.strings b/platform/ios/resources/uk.lproj/Localizable.strings
index 86873c69c0..8392f945b8 100644
--- a/platform/ios/resources/uk.lproj/Localizable.strings
+++ b/platform/ios/resources/uk.lproj/Localizable.strings
@@ -10,6 +10,9 @@
/* No comment provided by engineer. */
"CANCEL" = "Скасувати";
+/* Accessibility hint for closing the selected annotation’s callout view and returning to the map */
+"CLOSE_CALLOUT_A11Y_HINT" = "Повернутись до мапи";
+
/* Accessibility hint */
"COMPASS_A11Y_HINT" = "Обертає напрямок мапи на північ";
@@ -31,6 +34,12 @@
/* Accessibility label */
"INFO_A11Y_LABEL" = "Про мапу";
+/* User-friendly error description */
+"LOAD_MAP_FAILED_DESC" = "Неможливо завантажити мапу через невідому помилку.";
+
+/* User-friendly error description */
+"LOAD_STYLE_FAILED_DESC" = "Неможливо завантажити мапу, бо неможливо завантажити стиль.";
+
/* Accessibility label */
"LOGO_A11Y_LABEL" = "Mapbox";
@@ -38,13 +47,22 @@
"MAP_A11Y_LABEL" = "Мапа";
/* Map accessibility value */
-"MAP_A11Y_VALUE" = "Zoom %1$dx\n%2$ld annotation(s) visible";
+"MAP_A11Y_VALUE" = "Масштаб %1$dx\n%2$ld підпис(ів) показано";
+
+/* User-friendly error description */
+"PARSE_STYLE_FAILED_DESC" = "Неможливо завантажити мапу, через помилки в стилі.";
/* Action sheet title */
"SDK_NAME" = "Mapbox iOS SDK";
+/* Developer-only SDK update notification; {latest version, in format x.x.x} */
+"SDK_UPDATE_AVAILABLE" = "Доступна версія %@ Mapbox iOS SDK:";
+
+/* User-friendly error description */
+"STYLE_NOT_FOUND_DESC" = "Неможливо завантажити мапу, бо неможливо знайти стиль або він несумісний.";
+
/* Telemetry prompt message */
-"TELEMETRY_DISABLED_MSG" = "Ви можете допомогти зробити мапи OpenStreetMap та Mapbox кращими надаючі анонімізовані дані про користування застосунком.";
+"TELEMETRY_DISABLED_MSG" = "Ви можете допомогти зробити мапи OpenStreetMap та Mapbox кращими надаючи анонімізовані дані про користування застосунком.";
/* Telemetry prompt button */
"TELEMETRY_DISABLED_OFF" = "Відмовитись";
@@ -53,7 +71,7 @@
"TELEMETRY_DISABLED_ON" = "Брати участь";
/* Telemetry prompt message */
-"TELEMETRY_ENABLED_MSG" = "Ви допомагаєте робити мапи OpenStreetMap та Mapbox кращими надаючі анонімізовані дані про користування застосунком.";
+"TELEMETRY_ENABLED_MSG" = "Ви допомагаєте робити мапи OpenStreetMap та Mapbox кращими надаючи анонімізовані дані про користування застосунком.";
/* Telemetry prompt button */
"TELEMETRY_ENABLED_OFF" = "Припинити участь";
@@ -68,7 +86,7 @@
"TELEMETRY_NAME" = "Телеметрія Mapbox";
/* Telemetry prompt title */
-"TELEMETRY_TITLE" = "Робить мапи Mapbox кращими";
+"TELEMETRY_TITLE" = "Робіть мапи Mapbox кращими";
/* Default user location annotation title */
"USER_DOT_TITLE" = "Ви тут";
diff --git a/platform/ios/resources/uk.lproj/Localizable.stringsdict b/platform/ios/resources/uk.lproj/Localizable.stringsdict
new file mode 100644
index 0000000000..a81b01477e
--- /dev/null
+++ b/platform/ios/resources/uk.lproj/Localizable.stringsdict
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>MAP_A11Y_VALUE</key>
+ <dict>
+ <key>NSStringLocalizedFormatKey</key>
+ <string>%#@level@
+%#@count@</string>
+ <key>level</key>
+ <dict>
+ <key>NSStringFormatSpecTypeKey</key>
+ <string>NSStringPluralRuleType</string>
+ <key>NSStringFormatValueTypeKey</key>
+ <string>d</string>
+ <key>one</key>
+ <string>Масштаб %dx</string>
+ <key>few</key>
+ <string>Масштаб %dx</string>
+ <key>other</key>
+ <string>Масштаб %dx</string>
+ </dict>
+ <key>count</key>
+ <dict>
+ <key>NSStringFormatSpecTypeKey</key>
+ <string>NSStringPluralRuleType</string>
+ <key>NSStringFormatValueTypeKey</key>
+ <string>ld</string>
+ <key>one</key>
+ <string>Показано %d підпис</string>
+ <key>few</key>
+ <string>Показано %d підписи</string>
+ <key>other</key>
+ <string>Показано %d підпис(ів)</string>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/platform/ios/resources/zh-Hant.lproj/Localizable.strings b/platform/ios/resources/zh-Hant.lproj/Localizable.strings
index 1b4e7416d9..585ff3dd27 100644
--- a/platform/ios/resources/zh-Hant.lproj/Localizable.strings
+++ b/platform/ios/resources/zh-Hant.lproj/Localizable.strings
@@ -1,4 +1,4 @@
-/* Accessibility hint */
+/* Accessibility hint */
"ANNOTATION_A11Y_HINT" = "顯示信息";
/* No comment provided by engineer. */
@@ -10,6 +10,9 @@
/* No comment provided by engineer. */
"CANCEL" = "取消";
+/* Accessibility hint for closing the selected annotation’s callout view and returning to the map */
+"CLOSE_CALLOUT_A11Y_HINT" = "回到地圖";
+
/* Accessibility hint */
"COMPASS_A11Y_HINT" = "旋轉地圖使正北朝上";
@@ -19,24 +22,24 @@
/* Compass abbreviation for north */
"COMPASS_NORTH" = "北";
-/* Copyright notice in attribution sheet */
-"COPY_MAPBOX" = "© Mapbox";
-
-/* Copyright notice in attribution sheet */
-"COPY_OSM" = "© OpenStreetMap";
-
/* Instructions in Interface Builder designable; {key}, {plist file name} */
-"DESIGNABLE" = "在%2$@中將你的access token設爲%1$@可在這裏顯示Mapbox上的地圖\n\n更多說明請見";
+"DESIGNABLE" = "在%2$@中將你的access token設爲%1$@即可顯示Mapbox上的地圖\n\n更多說明請見";
/* Setup documentation URL display string; keep as short as possible */
"FIRST_STEPS_URL" = "mapbox.com/help/first-steps-ios-sdk";
/* Accessibility hint */
-"INFO_A11Y_HINT" = "顯示致謝、用戶反饋及更多";
+"INFO_A11Y_HINT" = "顯示致謝、用戶意見表及更多";
/* Accessibility label */
"INFO_A11Y_LABEL" = "關於這個地圖";
+/* User-friendly error description */
+"LOAD_MAP_FAILED_DESC" = "發生不知名的錯誤,無法載入地圖。";
+
+/* User-friendly error description */
+"LOAD_STYLE_FAILED_DESC" = "載入樣式表時發生錯誤,無法載入地圖。";
+
/* Accessibility label */
"LOGO_A11Y_LABEL" = "Mapbox";
@@ -44,14 +47,20 @@
"MAP_A11Y_LABEL" = "地圖";
/* Map accessibility value */
-"MAP_A11Y_VALUE" = "地圖縮放%1$d倍\n有%2$ld處標記可見";
+"MAP_A11Y_VALUE" = "縮放地圖%1$d倍\n可顯示%2$ld處標記";
-/* Action in attribution sheet */
-"MAP_FEEDBACK" = "改進地圖";
+/* User-friendly error description */
+"PARSE_STYLE_FAILED_DESC" = "樣式表有毀損,無法載入地圖。";
/* Action sheet title */
"SDK_NAME" = "Mapbox iOS SDK";
+/* Developer-only SDK update notification; {latest version, in format x.x.x} */
+"SDK_UPDATE_AVAILABLE" = "Mapbox iOS SDK %@版現已開放下載:";
+
+/* User-friendly error description */
+"STYLE_NOT_FOUND_DESC" = "找不到樣式表或樣式表不兼容,無法載入地圖。";
+
/* Telemetry prompt message */
"TELEMETRY_DISABLED_MSG" = "你可以提供匿名數據來幫助OpenStreetMap和Mapbox的地圖變得更好。";
@@ -62,7 +71,7 @@
"TELEMETRY_DISABLED_ON" = "我要參與";
/* Telemetry prompt message */
-"TELEMETRY_ENABLED_MSG" = "你的匿名數據在幫助OpenStreetMap和Mapbox的地圖變得更好。";
+"TELEMETRY_ENABLED_MSG" = "你的匿名數據正在改善OpenStreetMap和Mapbox地圖。";
/* Telemetry prompt button */
"TELEMETRY_ENABLED_OFF" = "不再參與";
@@ -74,11 +83,11 @@
"TELEMETRY_MORE" = "詳細信息";
/* Action in attribution sheet */
-"TELEMETRY_NAME" = "Mapbox傳感數據";
+"TELEMETRY_NAME" = "Mapbox遙測";
/* Telemetry prompt title */
"TELEMETRY_TITLE" = "讓Mapbox地圖變得更好";
/* Default user location annotation title */
-"USER_DOT_TITLE" = "你在這裏";
+"USER_DOT_TITLE" = "你在這裡";
diff --git a/platform/ios/resources/zh-Hant.lproj/Localizable.stringsdict b/platform/ios/resources/zh-Hant.lproj/Localizable.stringsdict
new file mode 100644
index 0000000000..fc44e2e501
--- /dev/null
+++ b/platform/ios/resources/zh-Hant.lproj/Localizable.stringsdict
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>MAP_A11Y_VALUE</key>
+ <dict>
+ <key>NSStringLocalizedFormatKey</key>
+ <string>%#@level@
+%#@count@</string>
+ <key>level</key>
+ <dict>
+ <key>NSStringFormatSpecTypeKey</key>
+ <string>NSStringPluralRuleType</string>
+ <key>NSStringFormatValueTypeKey</key>
+ <string>d</string>
+ <key>other</key>
+ <string>縮放層級%dx</string>
+ </dict>
+ <key>count</key>
+ <dict>
+ <key>NSStringFormatSpecTypeKey</key>
+ <string>NSStringPluralRuleType</string>
+ <key>NSStringFormatValueTypeKey</key>
+ <string>ld</string>
+ <key>other</key>
+ <string>可見%d處標示</string>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/platform/ios/scripts/package.sh b/platform/ios/scripts/package.sh
index a5e2f87e20..3a7342034a 100755
--- a/platform/ios/scripts/package.sh
+++ b/platform/ios/scripts/package.sh
@@ -31,7 +31,9 @@ function step { >&2 echo -e "\033[1m\033[36m* $@\033[0m"; }
function finish { >&2 echo -en "\033[0m"; }
trap finish EXIT
-step "Configuring ${FORMAT:-dynamic and static} ${BUILDTYPE} framework for ${SDK}; symbols: ${SYMBOLS}"
+step "Configuring ${FORMAT:-dynamic and static} ${BUILDTYPE} framework for ${SDK} ${IOS_SDK_VERSION}; symbols: ${SYMBOLS}"
+
+xcodebuild -version
rm -rf ${OUTPUT}
if [[ ${BUILD_STATIC} == true ]]; then
diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h
index c48dd6b27b..da5c7155a5 100644
--- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h
+++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.h
@@ -1,7 +1,15 @@
#import <UIKit/UIKit.h>
#import "MGLUserLocationAnnotationView.h"
+const CGFloat MGLUserLocationAnnotationDotSize = 22.0;
+const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
+
+const CGFloat MGLUserLocationAnnotationPuckSize = 45.0;
+const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6;
+
+// Threshold in radians between heading indicator rotation updates.
+const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01;
+
@interface MGLFaux3DUserLocationAnnotationView : MGLUserLocationAnnotationView
@end
-
diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m
index 5f67f24f4e..7001569b30 100644
--- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m
+++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m
@@ -2,14 +2,9 @@
#import "MGLMapView.h"
#import "MGLUserLocation.h"
-
-const CGFloat MGLUserLocationAnnotationDotSize = 22.0;
-const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
-
-const CGFloat MGLUserLocationAnnotationPuckSize = 45.0;
-const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6;
-
-#pragma mark -
+#import "MGLUserLocationHeadingIndicator.h"
+#import "MGLUserLocationHeadingArrowLayer.h"
+#import "MGLUserLocationHeadingBeamLayer.h"
@implementation MGLFaux3DUserLocationAnnotationView
{
@@ -18,14 +13,13 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
CALayer *_puckDot;
CAShapeLayer *_puckArrow;
- CALayer *_headingIndicatorLayer;
- CAShapeLayer *_headingIndicatorMaskLayer;
+ CALayer<MGLUserLocationHeadingIndicator> *_headingIndicatorLayer;
CALayer *_accuracyRingLayer;
CALayer *_dotBorderLayer;
CALayer *_dotLayer;
CALayer *_haloLayer;
- double _oldHeadingAccuracy;
+ CLLocationDirection _oldHeadingAccuracy;
CLLocationAccuracy _oldHorizontalAccuracy;
double _oldZoom;
double _oldPitch;
@@ -56,21 +50,18 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
- (void)setTintColor:(UIColor *)tintColor
{
+ CGColorRef newTintColor = [tintColor CGColor];
+
if (_puckModeActivated)
{
- _puckArrow.fillColor = [tintColor CGColor];
+ _puckArrow.fillColor = newTintColor;
}
else
{
- if (_accuracyRingLayer)
- {
- _accuracyRingLayer.backgroundColor = [tintColor CGColor];
- }
-
- _haloLayer.backgroundColor = [tintColor CGColor];
- _dotLayer.backgroundColor = [tintColor CGColor];
-
- _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
+ _accuracyRingLayer.backgroundColor = newTintColor;
+ _haloLayer.backgroundColor = newTintColor;
+ _dotLayer.backgroundColor = newTintColor;
+ [_headingIndicatorLayer updateTintColor:newTintColor];
}
}
@@ -80,7 +71,7 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
{
// disable implicit animation
[CATransaction begin];
- [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
+ [CATransaction setDisableActions:YES];
CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(self.mapView.camera.pitch), 1.0, 0, 0);
self.layer.sublayerTransform = t;
@@ -138,7 +129,6 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
self.layer.sublayers = nil;
_headingIndicatorLayer = nil;
- _headingIndicatorMaskLayer = nil;
_accuracyRingLayer = nil;
_haloLayer = nil;
_dotBorderLayer = nil;
@@ -225,70 +215,67 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
[self updateFrameWithSize:MGLUserLocationAnnotationDotSize];
}
- BOOL showHeadingIndicator = self.mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading;
-
- // update heading indicator
+ // heading indicator (tinted, beam or arrow)
//
+ BOOL headingTrackingModeEnabled = self.mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading;
+ BOOL showHeadingIndicator = self.mapView.showsUserHeadingIndicator || headingTrackingModeEnabled;
+
if (showHeadingIndicator)
{
_headingIndicatorLayer.hidden = NO;
+ CLLocationDirection headingAccuracy = self.userLocation.heading.headingAccuracy;
- // heading indicator (tinted, semi-circle)
- //
- if ( ! _headingIndicatorLayer && self.userLocation.heading.headingAccuracy)
+ if (([_headingIndicatorLayer isMemberOfClass:[MGLUserLocationHeadingBeamLayer class]] && ! headingTrackingModeEnabled) ||
+ ([_headingIndicatorLayer isMemberOfClass:[MGLUserLocationHeadingArrowLayer class]] && headingTrackingModeEnabled))
{
- CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize;
-
- _headingIndicatorLayer = [CALayer layer];
- _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize);
- _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0);
- _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
- _headingIndicatorLayer.contentsGravity = kCAGravityBottom;
- _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale;
- _headingIndicatorLayer.opacity = 0.4;
- _headingIndicatorLayer.shouldRasterize = YES;
- _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale;
- _headingIndicatorLayer.drawsAsynchronously = YES;
-
- [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer];
+ [_headingIndicatorLayer removeFromSuperlayer];
+ _headingIndicatorLayer = nil;
+ _oldHeadingAccuracy = -1;
}
- // heading indicator accuracy mask (fan-shaped)
- //
- if ( ! _headingIndicatorMaskLayer && self.userLocation.heading.headingAccuracy)
+ if ( ! _headingIndicatorLayer && headingAccuracy)
{
- _headingIndicatorMaskLayer = [CAShapeLayer layer];
- _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds;
- _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath];
-
- // apply the mask to the halo-radius-sized gradient layer
- _headingIndicatorLayer.mask = _headingIndicatorMaskLayer;
-
- _oldHeadingAccuracy = self.userLocation.heading.headingAccuracy;
-
+ if (headingTrackingModeEnabled)
+ {
+ _headingIndicatorLayer = [[MGLUserLocationHeadingBeamLayer alloc] initWithUserLocationAnnotationView:self];
+ [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer];
+ }
+ else
+ {
+ _headingIndicatorLayer = [[MGLUserLocationHeadingArrowLayer alloc] initWithUserLocationAnnotationView:self];
+ [self.layer addSublayer:_headingIndicatorLayer];
+ _headingIndicatorLayer.zPosition = 1;
+ }
}
- else if (_oldHeadingAccuracy != self.userLocation.heading.headingAccuracy)
- {
- // recalculate the clipping mask based on updated accuracy
- _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath];
- _oldHeadingAccuracy = self.userLocation.heading.headingAccuracy;
+ if (_oldHeadingAccuracy != headingAccuracy)
+ {
+ [_headingIndicatorLayer updateHeadingAccuracy:headingAccuracy];
+ _oldHeadingAccuracy = headingAccuracy;
}
if (self.userLocation.heading.trueHeading >= 0)
{
- _headingIndicatorLayer.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, -MGLRadiansFromDegrees(self.mapView.direction - self.userLocation.heading.trueHeading));
+ CGFloat rotation = -MGLRadiansFromDegrees(self.mapView.direction - self.userLocation.heading.trueHeading);
+
+ // Don't rotate if the change is imperceptible.
+ if (fabs(rotation) > MGLUserLocationHeadingUpdateThreshold)
+ {
+ [CATransaction begin];
+ [CATransaction setDisableActions:YES];
+
+ _headingIndicatorLayer.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, rotation);
+
+ [CATransaction commit];
+ }
}
}
else
{
[_headingIndicatorLayer removeFromSuperlayer];
- [_headingIndicatorMaskLayer removeFromSuperlayer];
_headingIndicatorLayer = nil;
- _headingIndicatorMaskLayer = nil;
}
-
// update accuracy ring (if zoom or horizontal accuracy have changed)
//
if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.userLocation.location.horizontalAccuracy))
@@ -301,10 +288,10 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
_accuracyRingLayer.hidden = NO;
// disable implicit animation of the accuracy ring, unless triggered by a change in accuracy
- id shouldDisableActions = (_oldHorizontalAccuracy == self.userLocation.location.horizontalAccuracy) ? (id)kCFBooleanTrue : (id)kCFBooleanFalse;
+ BOOL shouldDisableActions = _oldHorizontalAccuracy == self.userLocation.location.horizontalAccuracy;
[CATransaction begin];
- [CATransaction setValue:shouldDisableActions forKey:kCATransactionDisableActions];
+ [CATransaction setDisableActions:shouldDisableActions];
_accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize);
_accuracyRingLayer.cornerRadius = accuracyRingSize / 2;
@@ -464,65 +451,4 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
return self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude] * 2.0;
}
-- (UIImage *)headingIndicatorTintedGradientImage
-{
- UIImage *image;
-
- CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0;
-
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0);
-
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- CGContextRef context = UIGraphicsGetCurrentContext();
-
- // gradient from the tint color to no-alpha tint color
- CGFloat gradientLocations[] = {0.0, 1.0};
- CGGradientRef gradient = CGGradientCreateWithColors(
- colorSpace,
- (__bridge CFArrayRef)@[
- (id)[self.mapView.tintColor CGColor],
- (id)[[self.mapView.tintColor colorWithAlphaComponent:0] CGColor]],
- gradientLocations);
-
- // draw the gradient from the center point to the edge (full halo radius)
- CGPoint centerPoint = CGPointMake(haloRadius, haloRadius);
- CGContextDrawRadialGradient(context, gradient,
- centerPoint, 0.0,
- centerPoint, haloRadius,
- kNilOptions);
-
- image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- CGGradientRelease(gradient);
- CGColorSpaceRelease(colorSpace);
-
- return image;
-}
-
-- (UIBezierPath *)headingIndicatorClippingMask
-{
- CGFloat accuracy = self.userLocation.heading.headingAccuracy;
-
- // size the mask using accuracy, but keep within a good display range
- CGFloat clippingDegrees = 90 - accuracy;
- clippingDegrees = fmin(clippingDegrees, 70); // most accurate
- clippingDegrees = fmax(clippingDegrees, 10); // least accurate
-
- CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
- UIBezierPath *ovalPath = UIBezierPath.bezierPath;
-
- // clip the oval to ± incoming accuracy degrees (converted to radians), from the top
- [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))
- radius:CGRectGetWidth(ovalRect) / 2.0
- startAngle:MGLRadiansFromDegrees(-180 + clippingDegrees)
- endAngle:MGLRadiansFromDegrees(-clippingDegrees)
- clockwise:YES];
-
- [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
- [ovalPath closePath];
-
- return ovalPath;
-}
-
@end
diff --git a/platform/ios/src/MGLMapView+IBAdditions.h b/platform/ios/src/MGLMapView+IBAdditions.h
index 3766d044d8..d02c938c57 100644
--- a/platform/ios/src/MGLMapView+IBAdditions.h
+++ b/platform/ios/src/MGLMapView+IBAdditions.h
@@ -41,6 +41,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) IBInspectable BOOL allowsRotating;
@property (nonatomic) IBInspectable BOOL allowsTilting;
@property (nonatomic) IBInspectable BOOL showsUserLocation;
+@property (nonatomic) IBInspectable BOOL showsHeading;
@end
diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h
index c3ffe1983e..528606fd4e 100644
--- a/platform/ios/src/MGLMapView.h
+++ b/platform/ios/src/MGLMapView.h
@@ -370,6 +370,23 @@ MGL_EXPORT IB_DESIGNABLE
- (void)setUserLocationVerticalAlignment:(MGLAnnotationVerticalAlignment)alignment animated:(BOOL)animated;
/**
+ A Boolean value indicating whether the user location annotation may display a
+ permanent heading indicator.
+
+ Setting this property to `YES` causes the default user location annotation to
+ appear and always show an arrow-shaped heading indicator, if heading is
+ available. This property does not rotate the map; for that, see
+ `MGLUserTrackingModeFollowWithHeading`.
+
+ This property has no effect when `userTrackingMode` is
+ `MGLUserTrackingModeFollowWithHeading` or
+ `MGLUserTrackingModeFollowWithCourse`.
+
+ The default value of this property is `NO`.
+ */
+@property (nonatomic, assign) BOOL showsUserHeadingIndicator;
+
+/**
Whether the map view should display a heading calibration alert when necessary.
The default value is `YES`.
*/
@@ -589,7 +606,7 @@ MGL_EXPORT IB_DESIGNABLE
*
* The default minimumZoomLevel is 0.
*/
-@property (nonatomic) double minimumZoomLevel;
+@property (nonatomic) IBInspectable double minimumZoomLevel;
/**
* The maximum zoom level the map can be shown at.
@@ -600,7 +617,7 @@ MGL_EXPORT IB_DESIGNABLE
* The default maximumZoomLevel is 22. The upper bound for this property
* is 25.5.
*/
-@property (nonatomic) double maximumZoomLevel;
+@property (nonatomic) IBInspectable double maximumZoomLevel;
/**
The heading of the map, measured in degrees clockwise from true north.
@@ -1004,16 +1021,6 @@ MGL_EXPORT IB_DESIGNABLE
@property (nonatomic, readonly, nullable) NS_ARRAY_OF(id <MGLAnnotation>) *annotations;
/**
- The complete list of annotations associated with the receiver that are
- currently visible.
-
- The objects in this array must adopt the `MGLAnnotation` protocol. If no
- annotations are associated with the map view or if no annotations associated
- with the map view are currently visible, the value of this property is `nil`.
- */
-@property (nonatomic, readonly, nullable) NS_ARRAY_OF(id <MGLAnnotation>) *visibleAnnotations;
-
-/**
Adds an annotation to the map view.
@note `MGLMultiPolyline`, `MGLMultiPolygon`, `MGLShapeCollection`, and
@@ -1108,6 +1115,16 @@ MGL_EXPORT IB_DESIGNABLE
- (nullable __kindof MGLAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier;
/**
+ The complete list of annotations associated with the receiver that are
+ currently visible.
+
+ The objects in this array must adopt the `MGLAnnotation` protocol. If no
+ annotations are associated with the map view or if no annotations associated
+ with the map view are currently visible, the value of this property is `nil`.
+ */
+@property (nonatomic, readonly, nullable) NS_ARRAY_OF(id <MGLAnnotation>) *visibleAnnotations;
+
+/**
Returns the list of annotations associated with the receiver that intersect with
the given rectangle.
@@ -1276,6 +1293,11 @@ MGL_EXPORT IB_DESIGNABLE
`-[MGLVectorSource featuresInSourceLayersWithIdentifiers:predicate:]` and
`-[MGLShapeSource featuresMatchingPredicate:]` methods on the relevant sources.
+ The returned features may also include features corresponding to annotations.
+ These features are not object-equal to the `MGLAnnotation` objects that were
+ originally added to the map. To query the map for annotations, use
+ `visibleAnnotations` or `-[MGLMapView visibleAnnotationsInRect:]`.
+
@note Layer identifiers are not guaranteed to exist across styles or different
versions of the same style. Applications that use this API must first set
the style URL to an explicitly versioned style using a convenience method
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 49ef2bfb81..a03a5ad357 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -138,9 +138,6 @@ const CGFloat MGLAnnotationImagePaddingForCallout = 1;
const CGSize MGLAnnotationAccessibilityElementMinimumSize = CGSizeMake(10, 10);
-// Context for KVO observing UILayoutGuides.
-static void * MGLLayoutGuidesUpdatedContext = &MGLLayoutGuidesUpdatedContext;
-
/// Unique identifier representing a single annotation in mbgl.
typedef uint32_t MGLAnnotationTag;
@@ -242,10 +239,14 @@ public:
@property (nonatomic) EAGLContext *context;
@property (nonatomic) GLKView *glView;
@property (nonatomic) UIImageView *glSnapshotView;
+@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *scaleBarConstraints;
@property (nonatomic, readwrite) MGLScaleBar *scaleBar;
@property (nonatomic, readwrite) UIImageView *compassView;
+@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *compassViewConstraints;
@property (nonatomic, readwrite) UIImageView *logoView;
+@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *logoViewConstraints;
@property (nonatomic, readwrite) UIButton *attributionButton;
+@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *attributionButtonConstraints;
@property (nonatomic, readwrite) MGLStyle *style;
@property (nonatomic) UITapGestureRecognizer *singleTapGestureRecognizer;
@property (nonatomic) UITapGestureRecognizer *doubleTap;
@@ -302,8 +303,6 @@ public:
NSDate *_userLocationAnimationCompletionDate;
/// True if a willChange notification has been issued for shape annotation layers and a didChange notification is pending.
BOOL _isChangingAnnotationLayers;
- BOOL _isObservingTopLayoutGuide;
- BOOL _isObservingBottomLayoutGuide;
BOOL _isWaitingForRedundantReachableNotification;
BOOL _isTargetingInterfaceBuilder;
@@ -483,21 +482,31 @@ public:
_selectedAnnotationTag = MGLAnnotationTagNotFound;
_annotationsNearbyLastTap = {};
- // setup logo bug
+ // setup logo
//
UIImage *logo = [MGLMapView resourceImageNamed:@"mapbox"];
_logoView = [[UIImageView alloc] initWithImage:logo];
_logoView.accessibilityTraits = UIAccessibilityTraitStaticText;
_logoView.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"LOGO_A11Y_LABEL", nil, nil, @"Mapbox", @"Accessibility label");
+ _logoView.translatesAutoresizingMaskIntoConstraints = NO;
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
+ if ([_logoView respondsToSelector:@selector(accessibilityIgnoresInvertColors)]) { _logoView.accessibilityIgnoresInvertColors = YES; }
+#endif
[self addSubview:_logoView];
+ _logoViewConstraints = [NSMutableArray array];
// setup attribution
//
_attributionButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
_attributionButton.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"INFO_A11Y_LABEL", nil, nil, @"About this map", @"Accessibility label");
_attributionButton.accessibilityHint = NSLocalizedStringWithDefaultValue(@"INFO_A11Y_HINT", nil, nil, @"Shows credits, a feedback form, and more", @"Accessibility hint");
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
+ if ([_attributionButton respondsToSelector:@selector(accessibilityIgnoresInvertColors)]) { _attributionButton.accessibilityIgnoresInvertColors = YES; }
+#endif
[_attributionButton addTarget:self action:@selector(showAttribution) forControlEvents:UIControlEventTouchUpInside];
+ _attributionButton.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_attributionButton];
+ _attributionButtonConstraints = [NSMutableArray array];
[_attributionButton addObserver:self forKeyPath:@"hidden" options:NSKeyValueObservingOptionNew context:NULL];
// setup compass
@@ -509,12 +518,22 @@ public:
_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;
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
+ if ([_compassView respondsToSelector:@selector(accessibilityIgnoresInvertColors)]) { _compassView.accessibilityIgnoresInvertColors = YES; }
+#endif
[self addSubview:_compassView];
+ _compassViewConstraints = [NSMutableArray array];
// setup scale control
//
_scaleBar = [[MGLScaleBar alloc] init];
+ _scaleBar.translatesAutoresizingMaskIntoConstraints = NO;
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
+ if ([_scaleBar respondsToSelector:@selector(accessibilityIgnoresInvertColors)]) { _scaleBar.accessibilityIgnoresInvertColors = YES; }
+#endif
[self addSubview:_scaleBar];
+ _scaleBarConstraints = [NSMutableArray array];
// setup interaction
//
@@ -627,6 +646,9 @@ public:
_glView.contentScaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale];
_glView.layer.opaque = _opaque;
_glView.delegate = self;
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
+ if ([_glView respondsToSelector:@selector(accessibilityIgnoresInvertColors)]) { _glView.accessibilityIgnoresInvertColors = YES; }
+#endif
[_glView bindDrawable];
[self insertSubview:_glView atIndex:0];
_glView.contentMode = UIViewContentModeCenter;
@@ -674,48 +696,6 @@ public:
_isWaitingForRedundantReachableNotification = NO;
}
-- (void)willMoveToWindow:(UIWindow *)newWindow
-{
- [super willMoveToWindow:newWindow];
-
- if (newWindow) {
- [self addLayoutGuideObserversIfNeeded];
- } else {
- [self removeLayoutGuideObserversIfNeeded];
- }
-}
-
-- (void)addLayoutGuideObserversIfNeeded
-{
- UIViewController *viewController = self.viewControllerForLayoutGuides;
- BOOL useLayoutGuides = viewController.view && viewController.automaticallyAdjustsScrollViewInsets;
-
- if (useLayoutGuides && viewController.topLayoutGuide && !_isObservingTopLayoutGuide) {
- [(NSObject *)viewController.topLayoutGuide addObserver:self forKeyPath:@"bounds" options:0 context:(void *)&MGLLayoutGuidesUpdatedContext];
- _isObservingTopLayoutGuide = YES;
- }
-
- if (useLayoutGuides && viewController.bottomLayoutGuide && !_isObservingBottomLayoutGuide) {
- [(NSObject *)viewController.bottomLayoutGuide addObserver:self forKeyPath:@"bounds" options:0 context:(void *)&MGLLayoutGuidesUpdatedContext];
- _isObservingBottomLayoutGuide = YES;
- }
-}
-
-- (void)removeLayoutGuideObserversIfNeeded
-{
- UIViewController *viewController = self.viewControllerForLayoutGuides;
-
- if (_isObservingTopLayoutGuide) {
- [(NSObject *)viewController.topLayoutGuide removeObserver:self forKeyPath:@"bounds" context:(void *)&MGLLayoutGuidesUpdatedContext];
- _isObservingTopLayoutGuide = NO;
- }
-
- if (_isObservingBottomLayoutGuide) {
- [(NSObject *)viewController.bottomLayoutGuide removeObserver:self forKeyPath:@"bounds" context:(void *)&MGLLayoutGuidesUpdatedContext];
- _isObservingBottomLayoutGuide = NO;
- }
-}
-
- (void)dealloc
{
[_reachability stopNotifier];
@@ -724,8 +704,6 @@ public:
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_attributionButton removeObserver:self forKeyPath:@"hidden"];
- [self removeLayoutGuideObserversIfNeeded];
-
// Removing the annotations unregisters any outstanding KVO observers.
NSArray *annotations = self.annotations;
if (annotations)
@@ -751,6 +729,18 @@ public:
{
[EAGLContext setCurrentContext:nil];
}
+
+ [self.compassViewConstraints removeAllObjects];
+ self.compassViewConstraints = nil;
+
+ [self.scaleBarConstraints removeAllObjects];
+ self.scaleBarConstraints = nil;
+
+ [self.logoViewConstraints removeAllObjects];
+ self.logoViewConstraints = nil;
+
+ [self.attributionButtonConstraints removeAllObjects];
+ self.attributionButtonConstraints = nil;
}
- (void)setDelegate:(nullable id<MGLMapViewDelegate>)delegate
@@ -797,15 +787,207 @@ public:
return nil;
}
+- (void)updateConstraintsPreiOS11 {
+ // If we have a view controller reference and its automaticallyAdjustsScrollViewInsets
+ // is set to YES, use its view as the parent for constraints. -[MGLMapView adjustContentInset]
+ // already take top and bottom layout guides into account. If we don't have a reference, apply
+ // constraints against ourself to maintain placement of the subviews.
+ //
+ UIViewController *viewController = self.viewControllerForLayoutGuides;
+ BOOL useLayoutGuides = viewController.view && viewController.automaticallyAdjustsScrollViewInsets;
+ UIView *containerView = useLayoutGuides ? viewController.view : self;
+
+ // compass view
+ //
+ [containerView removeConstraints:self.compassViewConstraints];
+ [self.compassViewConstraints removeAllObjects];
+
+ if (useLayoutGuides) {
+ [self.compassViewConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self.compassView
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationGreaterThanOrEqual
+ toItem:viewController.topLayoutGuide
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0
+ constant:5.0 + self.contentInset.top]];
+ }
+ [self.compassViewConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self.compassView
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationGreaterThanOrEqual
+ toItem:self
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0
+ constant:5.0 + self.contentInset.top]];
+ [self.compassViewConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.compassView
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1.0
+ constant:5.0 + self.contentInset.right]];
+
+ [containerView addConstraints:self.compassViewConstraints];
+
+ // scale bar view
+ //
+ [containerView removeConstraints:self.scaleBarConstraints];
+ [self.scaleBarConstraints removeAllObjects];
+
+ if (useLayoutGuides) {
+ [self.scaleBarConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self.scaleBar
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationGreaterThanOrEqual
+ toItem:viewController.topLayoutGuide
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0
+ constant:5.0 + self.contentInset.top]];
+ }
+ [self.scaleBarConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self.scaleBar
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationGreaterThanOrEqual
+ toItem:self
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0
+ constant:5.0 + self.contentInset.top]];
+ [self.scaleBarConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self.scaleBar
+ attribute:NSLayoutAttributeLeft
+ relatedBy:NSLayoutRelationEqual
+ toItem:self
+ attribute:NSLayoutAttributeLeft
+ multiplier:1.0
+ constant:8.0 + self.contentInset.left]];
+
+ [containerView addConstraints:self.scaleBarConstraints];
+
+ // logo view
+ //
+ [containerView removeConstraints:self.logoViewConstraints];
+ [self.logoViewConstraints removeAllObjects];
+
+ if (useLayoutGuides) {
+ [self.logoViewConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:viewController.bottomLayoutGuide
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationGreaterThanOrEqual
+ toItem:self.logoView
+ attribute:NSLayoutAttributeBaseline
+ multiplier:1.0
+ constant:8.0 + self.contentInset.bottom]];
+ }
+ [self.logoViewConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationGreaterThanOrEqual
+ toItem:self.logoView
+ attribute:NSLayoutAttributeBaseline
+ multiplier:1
+ constant:8 + self.contentInset.bottom]];
+ [self.logoViewConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self.logoView
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:self
+ attribute:NSLayoutAttributeLeading
+ multiplier:1.0
+ constant:8.0 + self.contentInset.left]];
+ [containerView addConstraints:self.logoViewConstraints];
+
+ // attribution button
+ //
+ [containerView removeConstraints:self.attributionButtonConstraints];
+ [self.attributionButtonConstraints removeAllObjects];
+
+ if (useLayoutGuides) {
+ [self.attributionButtonConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:viewController.bottomLayoutGuide
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationGreaterThanOrEqual
+ toItem:self.attributionButton
+ attribute:NSLayoutAttributeBaseline
+ multiplier:1
+ constant:8 + self.contentInset.bottom]];
+ }
+ [self.attributionButtonConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationGreaterThanOrEqual
+ toItem:self.attributionButton
+ attribute:NSLayoutAttributeBaseline
+ multiplier:1
+ constant:8 + self.contentInset.bottom]];
+
+ [self.attributionButtonConstraints addObject:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.attributionButton
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1
+ constant:8 + self.contentInset.right]];
+ [containerView addConstraints:self.attributionButtonConstraints];
+}
+
- (void)updateConstraints
{
- [super updateConstraints];
- // If we have a view controller reference and its automaticallyAdjustsScrollViewInsets
- // is set to YES, -[MGLMapView adjustContentInset] takes top and bottom layout
- // guides into account. To get notified about changes to the layout guides,
- // we need to observe their bounds and re-layout accordingly.
- [self addLayoutGuideObserversIfNeeded];
+// If compiling with the iOS 11+ SDK
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
+ // If safeAreaLayoutGuide API exists
+ if ( [self respondsToSelector:@selector(safeAreaLayoutGuide)] ) {
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpartial-availability"
+ UILayoutGuide *safeAreaLayoutGuide = self.safeAreaLayoutGuide;
+#pragma clang diagnostic pop
+ // compass view
+ [self removeConstraints:self.compassViewConstraints];
+ [self.compassViewConstraints removeAllObjects];
+ [self.compassViewConstraints addObject:[self.compassView.topAnchor constraintEqualToAnchor:safeAreaLayoutGuide.topAnchor
+ constant:5.0 + self.contentInset.top]];
+ [self.compassViewConstraints addObject:[safeAreaLayoutGuide.rightAnchor constraintEqualToAnchor:self.compassView.rightAnchor
+ constant:8.0 + self.contentInset.right]];
+ [self addConstraints:self.compassViewConstraints];
+
+ // scale bar view
+ [self removeConstraints:self.scaleBarConstraints];
+ [self.scaleBarConstraints removeAllObjects];
+ [self.scaleBarConstraints addObject:[self.scaleBar.topAnchor constraintEqualToAnchor:safeAreaLayoutGuide.topAnchor
+ constant:5.0 + self.contentInset.top]];
+ [self.scaleBarConstraints addObject:[self.scaleBar.leftAnchor constraintEqualToAnchor:safeAreaLayoutGuide.leftAnchor
+ constant:8.0 + self.contentInset.left]];
+ [self addConstraints:self.scaleBarConstraints];
+
+ // logo view
+ [self removeConstraints:self.logoViewConstraints];
+ [self.logoViewConstraints removeAllObjects];
+ [self.logoViewConstraints addObject:[safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:self.logoView.bottomAnchor
+ constant:8.0 + self.contentInset.bottom]];
+ [self.logoViewConstraints addObject:[self.logoView.leftAnchor constraintEqualToAnchor:safeAreaLayoutGuide.leftAnchor
+ constant:8.0 + self.contentInset.left]];
+ [self addConstraints:self.logoViewConstraints];
+
+ // attribution button
+ [self removeConstraints:self.attributionButtonConstraints];
+ [self.attributionButtonConstraints removeAllObjects];
+ [self.attributionButtonConstraints addObject:[safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:self.attributionButton.bottomAnchor
+ constant:8.0 + self.contentInset.bottom]];
+ [self.attributionButtonConstraints addObject:[safeAreaLayoutGuide.rightAnchor constraintEqualToAnchor:self.attributionButton.rightAnchor
+ constant:8.0 + self.contentInset.right]];
+ [self addConstraints:self.attributionButtonConstraints];
+ } else {
+ [self updateConstraintsPreiOS11];
+ }
+#else
+ [self updateConstraintsPreiOS11];
+#endif
+
+ [super updateConstraints];
}
- (BOOL)isOpaque
@@ -835,8 +1017,6 @@ public:
[super layoutSubviews];
[self adjustContentInset];
-
- [self layoutOrnaments];
if (!_isTargetingInterfaceBuilder) {
_mbglMap->setSize([self size]);
@@ -844,44 +1024,15 @@ public:
if (self.compassView.alpha)
{
- [self updateHeadingForDeviceOrientation];
[self updateCompass];
}
- [self updateUserLocationAnnotationView];
-}
+ if (self.compassView.alpha || self.showsUserHeadingIndicator)
+ {
+ [self updateHeadingForDeviceOrientation];
+ }
-- (void)layoutOrnaments
-{
- // scale bar
- self.scaleBar.frame = {
- self.contentInset.left+8,
- self.contentInset.top+5,
- CGRectGetWidth(self.scaleBar.frame),
- CGRectGetHeight(self.scaleBar.frame)
- };
-
- // compass
- self.compassView.center = {
- .x = CGRectGetWidth(self.bounds)-CGRectGetMidX(self.compassView.bounds)-self.contentInset.right-5,
- .y = CGRectGetMidY(self.compassView.bounds)+self.contentInset.top+5
- };
-
- // logo bug
- self.logoView.frame = {
- self.contentInset.left+8,
- CGRectGetHeight(self.bounds)-8-self.contentInset.bottom-CGRectGetHeight(self.logoView.bounds),
- CGRectGetWidth(self.logoView.bounds),
- CGRectGetHeight(self.logoView.bounds)
- };
-
- // attribution
- self.attributionButton.frame = {
- CGRectGetWidth(self.bounds)-CGRectGetWidth(self.attributionButton.bounds)-self.contentInset.right-8,
- CGRectGetHeight(self.bounds)-CGRectGetHeight(self.attributionButton.bounds)-self.contentInset.bottom-8,
- CGRectGetWidth(self.attributionButton.bounds),
- CGRectGetHeight(self.attributionButton.bounds)
- };
+ [self updateUserLocationAnnotationView];
}
/// Updates `contentInset` to reflect the current window geometry.
@@ -2043,10 +2194,6 @@ public:
[self updateCalloutView];
}
}
- else if (context == MGLLayoutGuidesUpdatedContext && [keyPath isEqualToString:@"bounds"])
- {
- [self setNeedsLayout];
- }
}
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingZoomEnabled
@@ -4146,33 +4293,41 @@ public:
{
self.locationManager = [[CLLocationManager alloc] init];
- if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)] && [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined)
+ if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined)
{
- BOOL hasLocationDescription = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] || [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"];
- if (!hasLocationDescription)
+ BOOL requiresWhenInUseUsageDescription = [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){11,0,0}];
+ BOOL hasWhenInUseUsageDescription = !![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"];
+ BOOL hasAlwaysUsageDescription;
+ if (requiresWhenInUseUsageDescription)
+ {
+ hasAlwaysUsageDescription = !![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"] && hasWhenInUseUsageDescription;
+ }
+ else
{
- [NSException raise:@"Missing Location Services usage description" format:
- @"This app must have a value for NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription in its Info.plist."];
+ hasAlwaysUsageDescription = !![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"];
}
- if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"])
+ if (hasAlwaysUsageDescription)
{
[self.locationManager requestAlwaysAuthorization];
}
- else if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"])
+ else if (hasWhenInUseUsageDescription)
{
[self.locationManager requestWhenInUseAuthorization];
}
+ else
+ {
+ NSString *suggestedUsageKeys = requiresWhenInUseUsageDescription ?
+ @"NSLocationWhenInUseUsageDescription and (optionally) NSLocationAlwaysAndWhenInUseUsageDescription" :
+ @"NSLocationWhenInUseUsageDescription and/or NSLocationAlwaysUsageDescription";
+ [NSException raise:@"Missing Location Services usage description" format:@"This app must have a value for %@ in its Info.plist.", suggestedUsageKeys];
+ }
}
- self.locationManager.headingFilter = 5.0;
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
- if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading)
- {
- [self.locationManager startUpdatingHeading];
- }
+ [self validateUserHeadingUpdating];
}
else if ( ! shouldEnableLocationServices && self.locationManager)
{
@@ -4296,8 +4451,6 @@ public:
{
self.userTrackingState = MGLUserTrackingStatePossible;
- [self.locationManager stopUpdatingHeading];
-
// Immediately update the annotation view; other cases update inside
// the locationManager:didUpdateLocations: method.
[self updateUserLocationAnnotationView];
@@ -4310,14 +4463,6 @@ public:
self.userTrackingState = animated ? MGLUserTrackingStatePossible : MGLUserTrackingStateChanged;
self.showsUserLocation = YES;
- [self.locationManager stopUpdatingHeading];
-
- CLLocation *location = self.userLocation.location;
- if (location && self.userLocationAnnotationView)
- {
- [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated];
- }
-
break;
}
case MGLUserTrackingModeFollowWithHeading:
@@ -4334,19 +4479,21 @@ public:
[self setZoomLevel:self.currentMinimumZoom animated:YES];
}
- if (self.userLocationAnnotationView)
- {
- [self locationManager:self.locationManager didUpdateLocations:@[self.userLocation.location] animated:animated];
- }
-
- [self updateHeadingForDeviceOrientation];
-
- [self.locationManager startUpdatingHeading];
-
break;
}
}
+ if (_userTrackingMode != MGLUserTrackingModeNone)
+ {
+ CLLocation *location = self.userLocation.location;
+ if (location && self.userLocationAnnotationView)
+ {
+ [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated];
+ }
+ }
+
+ [self validateUserHeadingUpdating];
+
if ([self.delegate respondsToSelector:@selector(mapView:didChangeUserTrackingMode:animated:)])
{
[self.delegate mapView:self didChangeUserTrackingMode:_userTrackingMode animated:animated];
@@ -4385,14 +4532,43 @@ public:
if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
{
self.userTrackingState = MGLUserTrackingStatePossible;
- if (self.userLocation.location)
+
+ CLLocation *location = self.userLocation.location;
+ if (location)
{
- [self locationManager:self.locationManager didUpdateLocations:@[self.userLocation.location] animated:animated];
+ [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated];
}
}
}
}
+- (void)setShowsUserHeadingIndicator:(BOOL)showsUserHeadingIndicator
+{
+ _showsUserHeadingIndicator = showsUserHeadingIndicator;
+
+ if (_showsUserHeadingIndicator)
+ {
+ self.showsUserLocation = YES;
+
+ }
+ [self validateUserHeadingUpdating];
+}
+
+- (void)validateUserHeadingUpdating
+{
+ BOOL canShowPermanentHeadingIndicator = self.showsUserHeadingIndicator && self.userTrackingMode != MGLUserTrackingModeFollowWithCourse;
+
+ if (canShowPermanentHeadingIndicator || self.userTrackingMode == MGLUserTrackingModeFollowWithHeading)
+ {
+ [self updateHeadingForDeviceOrientation];
+ [self.locationManager startUpdatingHeading];
+ }
+ else
+ {
+ [self.locationManager stopUpdatingHeading];
+ }
+}
+
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
[self locationManager:manager didUpdateLocations:locations animated:YES];
@@ -4640,6 +4816,11 @@ public:
self.userLocation.heading = newHeading;
+ if (self.showsUserHeadingIndicator || self.userTrackingMode == MGLUserTrackingModeFollowWithHeading)
+ {
+ [self updateUserLocationAnnotationView];
+ }
+
if ([self.delegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)])
{
[self.delegate mapView:self didUpdateUserLocation:self.userLocation];
@@ -4676,30 +4857,39 @@ public:
{
// note that right/left device and interface orientations are opposites (see UIApplication.h)
//
+ CLDeviceOrientation orientation;
switch ([[UIApplication sharedApplication] statusBarOrientation])
{
case (UIInterfaceOrientationLandscapeLeft):
{
- self.locationManager.headingOrientation = CLDeviceOrientationLandscapeRight;
+ orientation = CLDeviceOrientationLandscapeRight;
break;
}
case (UIInterfaceOrientationLandscapeRight):
{
- self.locationManager.headingOrientation = CLDeviceOrientationLandscapeLeft;
+ orientation = CLDeviceOrientationLandscapeLeft;
break;
}
case (UIInterfaceOrientationPortraitUpsideDown):
{
- self.locationManager.headingOrientation = CLDeviceOrientationPortraitUpsideDown;
+ orientation = CLDeviceOrientationPortraitUpsideDown;
break;
}
case (UIInterfaceOrientationPortrait):
default:
{
- self.locationManager.headingOrientation = CLDeviceOrientationPortrait;
+ orientation = CLDeviceOrientationPortrait;
break;
}
}
+
+ // Setting the location manager's heading orientation causes it to send
+ // a heading event, which in turn makes us redraw, which kicks off a
+ // loop... so don't do that. rdar://34059173
+ if (self.locationManager.headingOrientation != orientation)
+ {
+ self.locationManager.headingOrientation = orientation;
+ }
}
}
@@ -5719,4 +5909,19 @@ private:
self.pitchEnabled = allowsTilting;
}
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingShowsHeading
+{
+ return [NSSet setWithObject:@"showsUserHeadingIndicator"];
+}
+
+- (BOOL)showsHeading
+{
+ return self.showsUserHeadingIndicator;
+}
+
+- (void)setShowsHeading:(BOOL)showsHeading
+{
+ self.showsUserHeadingIndicator = showsHeading;
+}
+
@end
diff --git a/platform/ios/src/MGLUserLocation.h b/platform/ios/src/MGLUserLocation.h
index 8c6fe46136..91abadbcb7 100644
--- a/platform/ios/src/MGLUserLocation.h
+++ b/platform/ios/src/MGLUserLocation.h
@@ -20,8 +20,7 @@ MGL_EXPORT
/**
The current location of the device. (read-only)
- This property contains `nil` if the map view is not currently showing the user
- location or if the user’s location has not yet been determined.
+ This property returns `nil` if the user’s location has not yet been determined.
*/
@property (nonatomic, readonly, nullable) CLLocation *location;
diff --git a/platform/ios/src/MGLUserLocation.m b/platform/ios/src/MGLUserLocation.m
index 1c9649c09e..074d138a72 100644
--- a/platform/ios/src/MGLUserLocation.m
+++ b/platform/ios/src/MGLUserLocation.m
@@ -19,7 +19,6 @@ NS_ASSUME_NONNULL_END
{
if (self = [super init])
{
- _location = [[CLLocation alloc] initWithLatitude:MAXFLOAT longitude:MAXFLOAT];
_mapView = mapView;
}
@@ -102,7 +101,7 @@ NS_ASSUME_NONNULL_END
- (CLLocationCoordinate2D)coordinate
{
- return self.location.coordinate;
+ return _location ? _location.coordinate : kCLLocationCoordinate2DInvalid;
}
- (NSString *)title
diff --git a/platform/ios/src/MGLUserLocationHeadingArrowLayer.h b/platform/ios/src/MGLUserLocationHeadingArrowLayer.h
new file mode 100644
index 0000000000..6c01356944
--- /dev/null
+++ b/platform/ios/src/MGLUserLocationHeadingArrowLayer.h
@@ -0,0 +1,11 @@
+#import <QuartzCore/QuartzCore.h>
+#import "MGLUserLocationAnnotationView.h"
+#import "MGLUserLocationHeadingIndicator.h"
+
+@interface MGLUserLocationHeadingArrowLayer : CAShapeLayer <MGLUserLocationHeadingIndicator>
+
+- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView;
+- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy;
+- (void)updateTintColor:(CGColorRef)color;
+
+@end
diff --git a/platform/ios/src/MGLUserLocationHeadingArrowLayer.m b/platform/ios/src/MGLUserLocationHeadingArrowLayer.m
new file mode 100644
index 0000000000..912ce30c35
--- /dev/null
+++ b/platform/ios/src/MGLUserLocationHeadingArrowLayer.m
@@ -0,0 +1,59 @@
+#import "MGLUserLocationHeadingArrowLayer.h"
+
+#import "MGLFaux3DUserLocationAnnotationView.h"
+#import "MGLGeometry.h"
+
+const CGFloat MGLUserLocationHeadingArrowSize = 6;
+
+@implementation MGLUserLocationHeadingArrowLayer
+
+- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView
+{
+ CGFloat size = userLocationView.bounds.size.width + MGLUserLocationHeadingArrowSize;
+
+ self = [super init];
+ self.bounds = CGRectMake(0, 0, size, size);
+ self.position = CGPointMake(CGRectGetMidX(userLocationView.bounds), CGRectGetMidY(userLocationView.bounds));
+ self.path = [self arrowPath];
+ self.fillColor = userLocationView.tintColor.CGColor;
+ self.shouldRasterize = YES;
+ self.rasterizationScale = UIScreen.mainScreen.scale;
+ self.drawsAsynchronously = YES;
+
+ self.strokeColor = UIColor.whiteColor.CGColor;
+ self.lineWidth = 1.0;
+ self.lineJoin = kCALineJoinRound;
+
+ return self;
+}
+
+- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy
+{
+ // unimplemented
+}
+
+- (void)updateTintColor:(CGColorRef)color
+{
+ self.fillColor = color;
+}
+
+- (CGPathRef)arrowPath {
+ CGFloat center = roundf(CGRectGetMidX(self.bounds));
+ CGFloat size = MGLUserLocationHeadingArrowSize;
+
+ CGPoint top = CGPointMake(center, 0);
+ CGPoint left = CGPointMake(center - size, size);
+ CGPoint right = CGPointMake(center + size, size);
+ CGPoint middle = CGPointMake(center, size / M_PI);
+
+ UIBezierPath *bezierPath = [UIBezierPath bezierPath];
+ [bezierPath moveToPoint:top];
+ [bezierPath addLineToPoint:left];
+ [bezierPath addQuadCurveToPoint:right controlPoint:middle];
+ [bezierPath addLineToPoint:top];
+ [bezierPath closePath];
+
+ return bezierPath.CGPath;
+}
+
+@end
diff --git a/platform/ios/src/MGLUserLocationHeadingBeamLayer.h b/platform/ios/src/MGLUserLocationHeadingBeamLayer.h
new file mode 100644
index 0000000000..93f8ea17ab
--- /dev/null
+++ b/platform/ios/src/MGLUserLocationHeadingBeamLayer.h
@@ -0,0 +1,11 @@
+#import <QuartzCore/QuartzCore.h>
+#import "MGLUserLocationAnnotationView.h"
+#import "MGLUserLocationHeadingIndicator.h"
+
+@interface MGLUserLocationHeadingBeamLayer : CALayer <MGLUserLocationHeadingIndicator>
+
+- (MGLUserLocationHeadingBeamLayer *)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView;
+- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy;
+- (void)updateTintColor:(CGColorRef)color;
+
+@end
diff --git a/platform/ios/src/MGLUserLocationHeadingBeamLayer.m b/platform/ios/src/MGLUserLocationHeadingBeamLayer.m
new file mode 100644
index 0000000000..efe7e4db93
--- /dev/null
+++ b/platform/ios/src/MGLUserLocationHeadingBeamLayer.m
@@ -0,0 +1,104 @@
+#import "MGLUserLocationHeadingBeamLayer.h"
+
+#import "MGLFaux3DUserLocationAnnotationView.h"
+#import "MGLGeometry.h"
+
+@implementation MGLUserLocationHeadingBeamLayer
+{
+ CAShapeLayer *_maskLayer;
+}
+
+- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView
+{
+ CGFloat size = MGLUserLocationAnnotationHaloSize;
+
+ self = [super init];
+ self.bounds = CGRectMake(0, 0, size, size);
+ self.position = CGPointMake(CGRectGetMidX(userLocationView.bounds), CGRectGetMidY(userLocationView.bounds));
+ self.contents = (__bridge id)[self gradientImageWithTintColor:userLocationView.tintColor.CGColor];
+ self.contentsGravity = kCAGravityBottom;
+ self.contentsScale = UIScreen.mainScreen.scale;
+ self.opacity = 0.4;
+ self.shouldRasterize = YES;
+ self.rasterizationScale = UIScreen.mainScreen.scale;
+ self.drawsAsynchronously = YES;
+
+ _maskLayer = [CAShapeLayer layer];
+ _maskLayer.frame = self.bounds;
+ _maskLayer.path = [self clippingMaskForAccuracy:0];
+ self.mask = _maskLayer;
+
+ return self;
+}
+
+- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy
+{
+ // recalculate the clipping mask based on updated accuracy
+ _maskLayer.path = [self clippingMaskForAccuracy:accuracy];
+}
+
+- (void)updateTintColor:(CGColorRef)color
+{
+ // redraw the raw tinted gradient
+ self.contents = (__bridge id)[self gradientImageWithTintColor:color];
+}
+
+- (CGImageRef)gradientImageWithTintColor:(CGColorRef)tintColor
+{
+ UIImage *image;
+
+ CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0;
+
+ UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0);
+
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ CGContextRef context = UIGraphicsGetCurrentContext();
+
+ // gradient from the tint color to no-alpha tint color
+ CGFloat gradientLocations[] = {0.0, 1.0};
+ CGGradientRef gradient = CGGradientCreateWithColors(
+ colorSpace,
+ (__bridge CFArrayRef)@[(__bridge id)tintColor,
+ (id)CFBridgingRelease(CGColorCreateCopyWithAlpha(tintColor, 0))],
+ gradientLocations);
+
+ // draw the gradient from the center point to the edge (full halo radius)
+ CGPoint centerPoint = CGPointMake(haloRadius, haloRadius);
+ CGContextDrawRadialGradient(context, gradient,
+ centerPoint, 0.0,
+ centerPoint, haloRadius,
+ kNilOptions);
+
+ image = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ CGGradientRelease(gradient);
+ CGColorSpaceRelease(colorSpace);
+
+ return image.CGImage;
+}
+
+- (CGPathRef)clippingMaskForAccuracy:(CGFloat)accuracy
+{
+ // size the mask using accuracy, but keep within a good display range
+ CGFloat clippingDegrees = 90 - accuracy;
+ clippingDegrees = fmin(clippingDegrees, 70); // most accurate
+ clippingDegrees = fmax(clippingDegrees, 10); // least accurate
+
+ CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
+ UIBezierPath *ovalPath = UIBezierPath.bezierPath;
+
+ // clip the oval to ± incoming accuracy degrees (converted to radians), from the top
+ [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))
+ radius:CGRectGetWidth(ovalRect) / 2.0
+ startAngle:MGLRadiansFromDegrees(-180 + clippingDegrees)
+ endAngle:MGLRadiansFromDegrees(-clippingDegrees)
+ clockwise:YES];
+
+ [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
+ [ovalPath closePath];
+
+ return ovalPath.CGPath;
+}
+
+@end
diff --git a/platform/ios/src/MGLUserLocationHeadingIndicator.h b/platform/ios/src/MGLUserLocationHeadingIndicator.h
new file mode 100644
index 0000000000..61476b96a2
--- /dev/null
+++ b/platform/ios/src/MGLUserLocationHeadingIndicator.h
@@ -0,0 +1,10 @@
+#import <QuartzCore/QuartzCore.h>
+#import "MGLUserLocationAnnotationView.h"
+
+@protocol MGLUserLocationHeadingIndicator <NSObject>
+
+- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView;
+- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy;
+- (void)updateTintColor:(CGColorRef)color;
+
+@end
diff --git a/platform/ios/uitest/KIF b/platform/ios/uitest/KIF
-Subproject 973a4cb653b54c3e8b2c0681f4097568ff0ac34
+Subproject c40b1048a6f35c6fd90376846a1a933844516b3
diff --git a/platform/ios/uitest/OHHTTPStubs b/platform/ios/uitest/OHHTTPStubs
-Subproject deed01a1592210a4c37f3f5c5f2b32fe0e41c60
+Subproject 4dc6f36375f78c0b3cfe58d90bb8a4e21df5196