From 80e98fd9532d2155c8bb1935359cc5451492a9db Mon Sep 17 00:00:00 2001 From: Petr Pokorny Date: Mon, 22 Feb 2021 13:01:41 +0100 Subject: [PATCH] Cherry pick changes from mapbox-gl-native-ios. resolves #29 --- platform/ios/platform/darwin/src/MGLEvent.h | 41 ++++ platform/ios/platform/darwin/src/MGLEvent.mm | 80 +++++++ .../platform/darwin/src/MGLEvent_Private.h | 11 + .../platform/darwin/src/MGLLocationManager.h | 31 +++ .../platform/darwin/src/MGLLocationManager.m | 31 +++ .../ios/platform/darwin/src/MGLObserver.h | 65 ++++++ .../ios/platform/darwin/src/MGLObserver.mm | 101 +++++++++ .../platform/darwin/src/MGLObserver_Private.h | 40 ++++ .../ios/platform/darwin/src/MGLStyleValue.mm | 2 + .../darwin/src/MGLStyleValue_Private.h | 2 + platform/ios/platform/ios/CHANGELOG.md | 13 ++ platform/ios/platform/ios/app/Info.plist | 5 + .../ios/app/MBXOrnamentsViewController.m | 3 +- .../ios/platform/ios/app/MBXViewController.m | 56 ++++- .../ios/docs/guides/Info.plist Keys.md | 17 +- .../ios/ios.xcodeproj/project.pbxproj | 48 +++++ .../src/MGLFaux3DUserLocationAnnotationView.m | 198 ++++++++++++++++-- platform/ios/platform/ios/src/MGLMapView.h | 11 +- platform/ios/platform/ios/src/MGLMapView.mm | 87 ++++++++ .../ios/platform/ios/src/MGLMapViewDelegate.h | 23 ++ .../src/MGLUserLocationAnnotationViewStyle.h | 57 +++++ .../src/MGLUserLocationAnnotationViewStyle.m | 62 ++++++ platform/ios/platform/ios/src/Mapbox.h | 65 +++--- .../MGLMapViewDelegateIntegrationTests.swift | 4 + 24 files changed, 999 insertions(+), 54 deletions(-) create mode 100644 platform/ios/platform/darwin/src/MGLEvent.h create mode 100644 platform/ios/platform/darwin/src/MGLEvent.mm create mode 100644 platform/ios/platform/darwin/src/MGLEvent_Private.h create mode 100644 platform/ios/platform/darwin/src/MGLObserver.h create mode 100644 platform/ios/platform/darwin/src/MGLObserver.mm create mode 100644 platform/ios/platform/darwin/src/MGLObserver_Private.h create mode 100644 platform/ios/platform/ios/src/MGLUserLocationAnnotationViewStyle.h create mode 100644 platform/ios/platform/ios/src/MGLUserLocationAnnotationViewStyle.m diff --git a/platform/ios/platform/darwin/src/MGLEvent.h b/platform/ios/platform/darwin/src/MGLEvent.h new file mode 100644 index 000000000..be647c65d --- /dev/null +++ b/platform/ios/platform/darwin/src/MGLEvent.h @@ -0,0 +1,41 @@ +#import "MGLFoundation.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Type of event used when subscribing to and unsubscribing from an `MGLObservable`. + */ +typedef NSString *MGLEventType NS_TYPED_EXTENSIBLE_ENUM; + +// TODO: Doc +FOUNDATION_EXPORT MGL_EXPORT MGLEventType const MGLEventTypeResourceRequest; + + +/** + Generic Event used when notifying an `MGLObserver`. This is not intended nor + expected to be created by the application developer. It will be provided as + part of an `MGLObservable` notification. + */ +MGL_EXPORT +@interface MGLEvent: NSObject + +/// Type of an event. Matches an original event type used for a subscription. +@property (nonatomic, readonly, copy) MGLEventType type; + +/// Timestamp taken at the time of an event creation, relative to the Unix epoch. +@property (nonatomic, readonly) NSTimeInterval begin; + +/// Timestamp taken at the time of an event completion. For a non-interval +/// (single-shot) events, migth be equal to an event's `begin` timestamp. +/// Relative to the Unix epoch. +@property (nonatomic, readonly) NSTimeInterval end; + +/// Generic property for the event's data. Supported types are: `NSNumber` (int64, +/// uint64, bool, double), `NSString`, `NSArray`, `NSDictionary`. +@property (nonatomic, readonly, copy) id data; + +/// Test for equality. Note this compares all properties except `data`. +- (BOOL)isEqualToEvent:(MGLEvent *)otherEvent; +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/platform/ios/platform/darwin/src/MGLEvent.mm b/platform/ios/platform/darwin/src/MGLEvent.mm new file mode 100644 index 000000000..21330a87a --- /dev/null +++ b/platform/ios/platform/darwin/src/MGLEvent.mm @@ -0,0 +1,80 @@ +#import "MGLEvent_Private.h" +#import "MGLStyleValue_Private.h" + +const MGLEventType MGLEventTypeResourceRequest = @"resource-request"; + +#pragma mark - Event + +@implementation MGLEvent + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (instancetype)initWithEvent:(const mbgl::ObservableEvent&)event { + self = [super init]; + + if (!self) + return nil; + + // mbgl::ObservableEvent timestamps do not use the system_clock, i.e. they + // are not relative to the Unix epoch, so we'll need to offset appropriately + auto systemClockNow = std::chrono::system_clock::now(); + auto steadyClockNow = std::chrono::steady_clock::now(); + auto begin = std::chrono::time_point_cast(systemClockNow + + (event.begin - steadyClockNow)); + auto end = std::chrono::time_point_cast(systemClockNow + + (event.end - steadyClockNow)); + + auto beginTime = std::chrono::duration>(begin.time_since_epoch()).count(); + auto endTime = std::chrono::duration>(end.time_since_epoch()).count(); + + _type = (MGLEventType)[NSString stringWithUTF8String:event.type.c_str()]; + _begin = beginTime; + _end = endTime; + + // From value.md + // Supported types are `int`, `uint`, `bool`, `double`, `array`, `object` and `string`. + _data = MGLJSONObjectFromMBGLValue(event.data); + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p; type = %@, begin = %f, end = %f, data = %@>", + NSStringFromClass([self class]), (void *)self, + self.type, + self.begin, + self.end, + self.data]; +} + +#pragma mark - Equality + +- (NSUInteger)hash { + return self.type.hash ^ @(self.begin).hash ^ @(self.end).hash; +} + +- (BOOL)isEqualToEvent:(MGLEvent *)other { + if (self == other) + return YES; + + // Ignore the value at this moment. + return ((self.type == other.type) && + (self.begin == other.begin) && + (self.end == other.end)); +} + +- (BOOL)isEqual:(id)other { + if (self == other) + return YES; + + if (![other isKindOfClass:[MGLEvent class]]) { + return NO; + } + + return [self isEqualToEvent:(MGLEvent*)other]; +} + +@end \ No newline at end of file diff --git a/platform/ios/platform/darwin/src/MGLEvent_Private.h b/platform/ios/platform/darwin/src/MGLEvent_Private.h new file mode 100644 index 000000000..ca2f88fdf --- /dev/null +++ b/platform/ios/platform/darwin/src/MGLEvent_Private.h @@ -0,0 +1,11 @@ +#import +#import "MGLEvent.h" +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLEvent () +- (instancetype)initWithEvent:(const mbgl::ObservableEvent&)event; +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/platform/ios/platform/darwin/src/MGLLocationManager.h b/platform/ios/platform/darwin/src/MGLLocationManager.h index ecb919298..d9dfe3911 100644 --- a/platform/ios/platform/darwin/src/MGLLocationManager.h +++ b/platform/ios/platform/darwin/src/MGLLocationManager.h @@ -58,6 +58,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)setDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 +/** + Specifies the level of location accuracy the Maps SDK has permission to use. + + @note If the value of this property is `CLAccuracyAuthorizationFullAccuracy`, you can set the + `MGLLocationManager.desiredAccuracy` property to any value. If the value is `CLAccuracyAuthorizationReducedAccuracy`, + setting `MGLLocationManager.desiredAccuracy` to a value other than` kCLLocationAccuracyReduced` has no effect on + the location information. + */ +- (CLAccuracyAuthorization)accuracyAuthorization API_AVAILABLE(ios(14)); +#endif + /** Specifies the type of user activity associated with the location updates. @@ -78,6 +90,17 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)setActivityType:(CLActivityType)activityType; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 +/** + Requests the user's permission to temporarily use location update services + with full accuracy. + + @note If the user turned off location accuracy you may use this method to + request full accuracy for a session. + */ +- (void)requestTemporaryFullAccuracyAuthorizationWithPurposeKey:(NSString *)purposeKey API_AVAILABLE(ios(14)); +#endif + @required /** @@ -195,6 +218,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)locationManager:(id)manager didFailWithError:(nonnull NSError *)error; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 +/** + Notifies the delegate that the location authorization status has changed. + + @param manager The location manager reporting the change. + */ +- (void)locationManagerDidChangeAuthorization:(id)manager; +#endif @optional diff --git a/platform/ios/platform/darwin/src/MGLLocationManager.m b/platform/ios/platform/darwin/src/MGLLocationManager.m index 29e3ccaa3..cdee37773 100644 --- a/platform/ios/platform/darwin/src/MGLLocationManager.m +++ b/platform/ios/platform/darwin/src/MGLLocationManager.m @@ -38,7 +38,16 @@ } - (CLAuthorizationStatus)authorizationStatus { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 + if (@available(iOS 14.0, *)) { + return self.locationManager.authorizationStatus; + } else { + return kCLAuthorizationStatusNotDetermined; + } +#else return [CLLocationManager authorizationStatus]; + return [CLLocationManager authorizationStatus]; +#endif } - (void)setActivityType:(CLActivityType)activityType { @@ -49,6 +58,23 @@ return self.locationManager.activityType; } +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 +- (CLAccuracyAuthorization)accuracyAuthorization { + if (@available(iOS 14.0, *)) { + return self.locationManager.accuracyAuthorization; + } else { + return CLAccuracyAuthorizationFullAccuracy; + } +} + +- (void)requestTemporaryFullAccuracyAuthorizationWithPurposeKey:(NSString *)purposeKey { + if (@available(iOS 14.0, *)) { + [self.locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:purposeKey]; + } +} +#endif + + - (void)dismissHeadingCalibrationDisplay { [self.locationManager dismissHeadingCalibrationDisplay]; } @@ -113,4 +139,9 @@ } } +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 +- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager { + [self.delegate locationManagerDidChangeAuthorization:self]; +} +#endif @end diff --git a/platform/ios/platform/darwin/src/MGLObserver.h b/platform/ios/platform/darwin/src/MGLObserver.h new file mode 100644 index 000000000..b46829747 --- /dev/null +++ b/platform/ios/platform/darwin/src/MGLObserver.h @@ -0,0 +1,65 @@ +#import "MGLFoundation.h" +#import "MGLEvent.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - + +/** + Base class for observers used to receive notifications from an `MGLObservable`. + Subclasses should only override `-[MGLObserver notifyWithEvent:]`. + */ +MGL_EXPORT +@interface MGLObserver: NSObject + +/// A unique identifier that can be used to identify an observer. +@property (nonatomic, readonly) NSUInteger identifier; + +/// Primary notification method. +- (void)notifyWithEvent:(MGLEvent*)event; + +// TODO: document +- (BOOL)isEqualToObserver:(MGLObserver *)other; +@end + + +#pragma mark - + +/** + Protocol that yypes that wish to support subscriptions for events should conform + to. + */ +MGL_EXPORT +@protocol MGLObservable + +/** + Subscribes an `MGLObserver` to a provided set of event types. + `MGLObservable` will hold a strong reference to an `MGLObserver` instance, + therefore, in order to stop receiving notifications (and to avoid memory leaks), + the caller must call unsubscribe with the `MGLObserver` instance used for + the initial subscription. + + @param observer an MGLObserver + @param events a set of event types to subscribe to. + */ +- (void)subscribeForObserver:(nonnull MGLObserver *)observer + events:(nonnull NSSet *)events; + +/** + Unsubscribes an `MGLObserver` from a provided set of event types. + + @param observer an MGLObserver + @param events a set of event types to unsubscribe from. + */ +- (void)unsubscribeForObserver:(nonnull MGLObserver *)observer + events:(nonnull NSSet *)events; +/** + Unsubscribes an `MGLObserver` from all events (and release the strong reference). + + @param observer an MGLObserver + */ +- (void)unsubscribeForObserver:(nonnull MGLObserver *)observer; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/platform/ios/platform/darwin/src/MGLObserver.mm b/platform/ios/platform/darwin/src/MGLObserver.mm new file mode 100644 index 000000000..54ae76ab5 --- /dev/null +++ b/platform/ios/platform/darwin/src/MGLObserver.mm @@ -0,0 +1,101 @@ +#import "MGLObserver_Private.h" +#import "MGLLoggingConfiguration_Private.h" +#import "MGLEvent_Private.h" + +#pragma mark - Native C++ peer object + +namespace mbgl { +namespace darwin { + +void Observer::notify(const ObservableEvent& event) { + + if (!observer) { + MGLLogWarning(@"Platform observer has been deallocated"); + return; + } + + [observer notifyWithEvent:[[MGLEvent alloc] initWithEvent:event]]; +} + +std::size_t Observer::id() const { + if (!observer) { + MGLLogWarning(@"Platform observer has been deallocated"); + return 0; + } + + return static_cast(observer.identifier); +} + +} +} + +#pragma mark - Cocoa observer + +@implementation MGLObserver ++ (NSUInteger)nextIdentifier { + static NSUInteger identifier; + return ++identifier; +} + +- (void)dealloc { + MGLAssert(!_observing, @"Ensure the observer is unsubscribed before deallocating"); +} + +- (instancetype)init { + self = [super init]; + + if (!self) return nil; + + _identifier = [MGLObserver nextIdentifier]; + _peer = std::make_shared(self); + + return self; +} + +- (void)notifyWithEvent:(MGLEvent*)event { + if (self.notificationHandler) { + self.notificationHandler(event); + } +} + +- (BOOL)isEqualToObserver:(MGLObserver *)other { + if (self == other) + return YES; + + if (self.identifier != other.identifier) + return NO; + + return (self.peer == other.peer); +} + +- (BOOL)isEqual:(id)other { + if (self == other) + return YES; + + if (![other isKindOfClass:[MGLObserver class]]) { + return NO; + } + + return [self isEqualToObserver:(MGLObserver*)other]; +} + +- (NSUInteger)hash { + // Rotate the address + NSUInteger peerHash = reinterpret_cast(self.peer.get()); + + NSUInteger width = (sizeof(NSUInteger) * __CHAR_BIT__); + NSUInteger shift = self.identifier % width; + + NSUInteger newHash = (peerHash << shift) | (peerHash >> (width - shift)); + return newHash; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p; identifier = %lu, peer = %p, hash = %lu>", + NSStringFromClass([self class]), (void *)self, + (unsigned long)self.identifier, + self.peer.get(), + (unsigned long)[self hash]]; +} + +@end \ No newline at end of file diff --git a/platform/ios/platform/darwin/src/MGLObserver_Private.h b/platform/ios/platform/darwin/src/MGLObserver_Private.h new file mode 100644 index 000000000..2fe8fcb6c --- /dev/null +++ b/platform/ios/platform/darwin/src/MGLObserver_Private.h @@ -0,0 +1,40 @@ +#import + +#import "MGLObserver.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Native C++ peer object + +namespace mbgl { +namespace darwin { + +class Observer : public mbgl::Observer { +public: + Observer(MGLObserver *observer_): observer(observer_) {} + virtual ~Observer() = default; + virtual void notify(const ObservableEvent& event); + virtual std::size_t id() const; + +protected: + + /// Cocoa observer that this adapter bridges to. + __weak MGLObserver *observer = nullptr; +}; + +} +} + +#pragma mark - +@interface MGLObserver () +@property (nonatomic, assign) std::shared_ptr peer; + +// TODO: Consider making public +@property (nonatomic, copy) void (^notificationHandler)(MGLEvent *); + +// Debug property used for development. +@property (nonatomic, assign) BOOL observing; +@end + +NS_ASSUME_NONNULL_END + diff --git a/platform/ios/platform/darwin/src/MGLStyleValue.mm b/platform/ios/platform/darwin/src/MGLStyleValue.mm index 01ad108d7..f379e71b8 100644 --- a/platform/ios/platform/darwin/src/MGLStyleValue.mm +++ b/platform/ios/platform/darwin/src/MGLStyleValue.mm @@ -14,6 +14,8 @@ id MGLJSONObjectFromMBGLValue(const mbgl::Value &value) { return @(value); }, [](const int64_t value) { return @(value); + }, [](const uint64_t value) { + return @(value); }, [](const double value) { return @(value); }, [](const std::string &value) { diff --git a/platform/ios/platform/darwin/src/MGLStyleValue_Private.h b/platform/ios/platform/darwin/src/MGLStyleValue_Private.h index 43f0b9d28..cec6e1aae 100644 --- a/platform/ios/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/ios/platform/darwin/src/MGLStyleValue_Private.h @@ -35,6 +35,8 @@ namespace mbgl { } } +id MGLJSONObjectFromMBGLValue(const mbgl::Value &value); + NS_INLINE MGLTransition MGLTransitionFromOptions(const mbgl::style::TransitionOptions& options) { MGLTransition transition; transition.duration = MGLTimeIntervalFromDuration(options.duration.value_or(mbgl::Duration::zero())); diff --git a/platform/ios/platform/ios/CHANGELOG.md b/platform/ios/platform/ios/CHANGELOG.md index e0f535877..114aa8067 100644 --- a/platform/ios/platform/ios/CHANGELOG.md +++ b/platform/ios/platform/ios/CHANGELOG.md @@ -6,6 +6,19 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Features +* Added `MGLLocationManager.accuracyAuthorization` and `[MGLLocationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKeyproperty:]` to support iOS 14 location accuracy privacy changes. (cherry pick from [#361](https://github.com/mapbox/mapbox-gl-native-ios/pull/361)) +* Added `[MGLLocationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKeyproperty:]` to allow developers request just-in-time full-accuracy permissions. (cherry pick from [#361](https://github.com/mapbox/mapbox-gl-native-ios/pull/361)) +* Added `[MGLLocationManagerDelegate locationManagerDidChangeAuthorization:]` to let `MGLMapView` know about privacy changes. (cherry pick from [#376](https://github.com/mapbox/mapbox-gl-native-ios/pull/376)) +* Added `[MGLMapViewDelegate mapView:didChangeLocationManagerAuthorization:]` to allow developers adjust their apps to privacy settings changes. (cherry pick from [#376](https://github.com/mapbox/mapbox-gl-native-ios/pull/376)) +* Added `MGLObserver`, `MGLObservable` and `MGLEvent` to monitor events from the map view. (cherry pick from [#358](https://github.com/mapbox/mapbox-gl-native-ios/pull/358)) +* Added an approximate user location halo when `MGLLocationManager.accuracyAuthorization` is set to `CLAccuracyAuthorizationReducedAccuracy`. (cherry pick from [#381](https://github.com/mapbox/mapbox-gl-native-ios/pull/381)) +* The `MGLAccuracyAuthorizationDescription` as element of `NSLocationTemporaryUsageDescriptionDictionary` Info.plist key can now be set to describe why you request accuracy authorization. (cherry pick from [#392](https://github.com/mapbox/mapbox-gl-native-ios/pull/392)) +* Added `[MGLMapViewDelegate mapViewStyleForDefaultUserLocationAnnotationView:]` and `MGLUserLocationAnnotationViewStyle` class to allow developers customize the default user location annotation view UI style. (cherry pick from [#403](https://github.com/mapbox/mapbox-gl-native-ios/pull/403)) + +### Bug Fixes + +* Fixed an issue that caused a crash when custom location managers did not implement `MGLLocationManager.accuracyAuthorization`. (cherry pick from [#474](https://github.com/mapbox/mapbox-gl-native-ios/pull/474)) + ### Other * mapbox-gl-js submodule has been replaced with maplibre-gl-js diff --git a/platform/ios/platform/ios/app/Info.plist b/platform/ios/platform/ios/app/Info.plist index e7c23e481..b349ea331 100644 --- a/platform/ios/platform/ios/app/Info.plist +++ b/platform/ios/platform/ios/app/Info.plist @@ -24,6 +24,11 @@ 15256 LSRequiresIPhoneOS + NSLocationTemporaryUsageDescriptionDictionary + + MGLAccuracyAuthorizationDescription + MapLibre requires your precise location to help you navigate the map. + NSHumanReadableCopyright © 2014–2019 Mapbox NSLocationAlwaysUsageDescription diff --git a/platform/ios/platform/ios/app/MBXOrnamentsViewController.m b/platform/ios/platform/ios/app/MBXOrnamentsViewController.m index f451f647e..28dc79641 100644 --- a/platform/ios/platform/ios/app/MBXOrnamentsViewController.m +++ b/platform/ios/platform/ios/app/MBXOrnamentsViewController.m @@ -2,7 +2,7 @@ #import "MBXOrnamentsViewController.h" -@interface MBXOrnamentsViewController () +@interface MBXOrnamentsViewController () @property (nonatomic) MGLMapView *mapView; @property (nonatomic) NSTimer *timer; @@ -64,7 +64,6 @@ zoomLevel:16 direction:30 animated:NO]; - mapView.delegate = self; mapView.showsScale = YES; [self.view addSubview:mapView]; diff --git a/platform/ios/platform/ios/app/MBXViewController.m b/platform/ios/platform/ios/app/MBXViewController.m index af668317c..1c8a7519d 100644 --- a/platform/ios/platform/ios/app/MBXViewController.m +++ b/platform/ios/platform/ios/app/MBXViewController.m @@ -15,7 +15,6 @@ #import "MBXFrameTimeGraphView.h" #import "../src/MGLMapView_Experimental.h" - #import static const CLLocationCoordinate2D WorldTourDestinations[] = { @@ -193,6 +192,15 @@ CLLocationCoordinate2D randomWorldCoordinate() { @implementation MBXSpriteBackedAnnotation @end +@interface MBXTestObserver: MGLObserver +@end + +@implementation MBXTestObserver +- (void)notifyWithEvent:(MGLEvent *)event { + NSLog(@"Received event: %@", event); +} +@end + @interface MBXViewController () *helperWindows; @property (nonatomic) NSMutableArray *contentInsetsOverlays; - +@property (nonatomic) MBXTestObserver *testObserver; +@property (nonatomic, copy) void (^locationBlock)(void); @end @interface MGLMapView (MBXViewController) @@ -300,6 +309,15 @@ CLLocationCoordinate2D randomWorldCoordinate() { } } }]; + + // TODO: Replace with menu implementation + self.testObserver = [[MBXTestObserver alloc] init]; + [self.mapView subscribeForObserver:self.testObserver event:MGLEventTypeResourceRequest]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self.mapView unsubscribeForObserver:self.testObserver]; + self.testObserver = nil; + }); } - (UIInterfaceOrientationMask)supportedInterfaceOrientations @@ -1952,6 +1970,12 @@ CLLocationCoordinate2D randomWorldCoordinate() { } - (IBAction)locateUser:(id)sender +{ + [self nextTrackingMode:sender]; +} + + +- (void)nextTrackingMode:(id)sender { MGLUserTrackingMode nextMode; NSString *nextAccessibilityValue; @@ -2333,6 +2357,34 @@ CLLocationCoordinate2D randomWorldCoordinate() { } } +- (void)mapView:(nonnull MGLMapView *)mapView didChangeLocationManagerAuthorization:(nonnull id)manager { + if (@available(iOS 14, *)) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 + if (manager.authorizationStatus == kCLAuthorizationStatusDenied || manager.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy) { + [self alertAccuracyChanges]; + } +#endif + } +} + +- (void)alertAccuracyChanges { + UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Mapbox GL works best with your precise location." + message:@"You'll get turn-by-turn directions." + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:@"Turn On in Settings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + }]; + + UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"Keep Precise Location Off" style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + handler:nil]; + + [alert addAction:settingsAction]; + [alert addAction:defaultAction]; + [self presentViewController:alert animated:YES completion:nil]; +} + - (void)saveCurrentMapState:(__unused NSNotification *)notification { // The following properties can change after the view loads so we need to save their diff --git a/platform/ios/platform/ios/docs/guides/Info.plist Keys.md b/platform/ios/platform/ios/docs/guides/Info.plist Keys.md index 446045764..39dfceb08 100644 --- a/platform/ios/platform/ios/docs/guides/Info.plist Keys.md +++ b/platform/ios/platform/ios/docs/guides/Info.plist Keys.md @@ -10,6 +10,22 @@ Mapbox-hosted vector tiles and styles require an API access token, which you can As an alternative, you can use `MGLAccountManager.accessToken` to set a token in code. See [our guide](https://www.mapbox.com/help/ios-private-access-token/) for some tips on keeping access tokens in open source code private. +## MGLAccuracyAuthorizationDescription + +Set the Mapbox accuracy authorization description string as an element of `NSLocationTemporaryUsageDescriptionDictionary` to be used by the map to request authorization when the `MGLLocationManager.accuracyAuthorization` is set to `CLAccuracyAuthorizationReducedAccuracy`. Requesting accuracy authorization is available for devices running iOS 14.0 and above. + +Example: + +```xml +NSLocationTemporaryUsageDescriptionDictionary + + MGLAccuracyAuthorizationDescription + Mapbox requires your precise location to help you navigate the map. + +``` + +Remove `MGLAccuracyAuthorizationDescription` if you want to control when to request for accuracy authorization. + ## MGLMapboxAPIBaseURL Use this key if you need to customize the API base URL used throughout the SDK. If unset, the default Mapbox API is used. @@ -47,4 +63,3 @@ At runtime, you can obtain the value of this key using the `MGLOfflineStorage.da Beginning in version 4.0, the SDK also performs collision detection between style layers based on different sources by default. For the default behavior, omit the `MGLCollisionBehaviorPre4_0` key or set it to NO (`false`). This property may also be set using `[[NSUserDefaults standardUserDefaults] setObject:@(YES) forKey:@"MGLCollisionBehaviorPre4_0"]`; it will override any value specified in the `Info.plist`. - diff --git a/platform/ios/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/platform/ios/ios.xcodeproj/project.pbxproj index 62d5c2dbd..3cb975a8b 100644 --- a/platform/ios/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/platform/ios/ios.xcodeproj/project.pbxproj @@ -224,6 +224,22 @@ 40F887701D7A1E58008ECB67 /* MGLShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 40F8876F1D7A1DB8008ECB67 /* MGLShapeSource_Private.h */; }; 40F887711D7A1E59008ECB67 /* MGLShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 40F8876F1D7A1DB8008ECB67 /* MGLShapeSource_Private.h */; }; 40FDA76B1CCAAA6800442548 /* MBXAnnotationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */; }; + 4BC5767B25E3C8AB006E06EB /* MGLObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5767525E3C8AA006E06EB /* MGLObserver.h */; }; + 4BC5767C25E3C8AB006E06EB /* MGLObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5767525E3C8AA006E06EB /* MGLObserver.h */; }; + 4BC5767D25E3C8AB006E06EB /* MGLEvent_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5767625E3C8AB006E06EB /* MGLEvent_Private.h */; }; + 4BC5767E25E3C8AB006E06EB /* MGLEvent_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5767625E3C8AB006E06EB /* MGLEvent_Private.h */; }; + 4BC5767F25E3C8AB006E06EB /* MGLEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5767725E3C8AB006E06EB /* MGLEvent.mm */; }; + 4BC5768025E3C8AB006E06EB /* MGLEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5767725E3C8AB006E06EB /* MGLEvent.mm */; }; + 4BC5768125E3C8AB006E06EB /* MGLObserver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5767825E3C8AB006E06EB /* MGLObserver.mm */; }; + 4BC5768225E3C8AB006E06EB /* MGLObserver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5767825E3C8AB006E06EB /* MGLObserver.mm */; }; + 4BC5768325E3C8AB006E06EB /* MGLEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5767925E3C8AB006E06EB /* MGLEvent.h */; }; + 4BC5768425E3C8AB006E06EB /* MGLEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5767925E3C8AB006E06EB /* MGLEvent.h */; }; + 4BC5768525E3C8AB006E06EB /* MGLObserver_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5767A25E3C8AB006E06EB /* MGLObserver_Private.h */; }; + 4BC5768625E3C8AB006E06EB /* MGLObserver_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5767A25E3C8AB006E06EB /* MGLObserver_Private.h */; }; + 4BC5769325E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5769125E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h */; }; + 4BC5769425E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BC5769125E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h */; }; + 4BC5769525E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5769225E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m */; }; + 4BC5769625E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5769225E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m */; }; 550570C622958FB400228ECF /* MGLMapView+Impl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 550570C422958FB300228ECF /* MGLMapView+Impl.mm */; }; 550570C722958FB400228ECF /* MGLMapView+Impl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 550570C422958FB300228ECF /* MGLMapView+Impl.mm */; }; 550570C822958FB400228ECF /* MGLMapView+Impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 550570C522958FB400228ECF /* MGLMapView+Impl.h */; }; @@ -907,6 +923,14 @@ 40F8876F1D7A1DB8008ECB67 /* MGLShapeSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLShapeSource_Private.h; sourceTree = ""; }; 40FDA7691CCAAA6800442548 /* MBXAnnotationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXAnnotationView.h; sourceTree = ""; }; 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXAnnotationView.m; sourceTree = ""; }; + 4BC5767525E3C8AA006E06EB /* MGLObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLObserver.h; sourceTree = ""; }; + 4BC5767625E3C8AB006E06EB /* MGLEvent_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLEvent_Private.h; sourceTree = ""; }; + 4BC5767725E3C8AB006E06EB /* MGLEvent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLEvent.mm; sourceTree = ""; }; + 4BC5767825E3C8AB006E06EB /* MGLObserver.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLObserver.mm; sourceTree = ""; }; + 4BC5767925E3C8AB006E06EB /* MGLEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLEvent.h; sourceTree = ""; }; + 4BC5767A25E3C8AB006E06EB /* MGLObserver_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLObserver_Private.h; sourceTree = ""; }; + 4BC5769125E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationAnnotationViewStyle.h; sourceTree = ""; }; + 4BC5769225E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationAnnotationViewStyle.m; sourceTree = ""; }; 550570C422958FB300228ECF /* MGLMapView+Impl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "MGLMapView+Impl.mm"; sourceTree = ""; }; 550570C522958FB400228ECF /* MGLMapView+Impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLMapView+Impl.h"; sourceTree = ""; }; 554180411D2E97DE00012372 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; @@ -1894,6 +1918,12 @@ 1F6A82A021360F9C00BA5B41 /* MGLLoggingConfiguration.h */, CF75A91422D85E860058A5C4 /* MGLLoggingConfiguration.mm */, 1F6A82A62138871900BA5B41 /* MGLLoggingConfiguration_Private.h */, + 4BC5767625E3C8AB006E06EB /* MGLEvent_Private.h */, + 4BC5767925E3C8AB006E06EB /* MGLEvent.h */, + 4BC5767725E3C8AB006E06EB /* MGLEvent.mm */, + 4BC5767A25E3C8AB006E06EB /* MGLObserver_Private.h */, + 4BC5767525E3C8AA006E06EB /* MGLObserver.h */, + 4BC5767825E3C8AB006E06EB /* MGLObserver.mm */, ); name = Foundation; path = ../darwin/src; @@ -1919,6 +1949,8 @@ 5580B45A229570A10091291B /* MGLMapView+OpenGL.mm */, DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */, DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, + 4BC5769125E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h */, + 4BC5769225E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m */, ); name = Kit; path = src; @@ -2182,6 +2214,7 @@ 357FE2DD1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */, 35D13AB71D3D15E300AFB4E0 /* MGLStyleLayer.h in Headers */, 07D947531F67488E00E37934 /* MGLComputedShapeSource_Private.h in Headers */, + 4BC5768325E3C8AB006E06EB /* MGLEvent.h in Headers */, 9654C1261FFC1AB900DB6A19 /* MGLPolyline_Private.h in Headers */, 40F887701D7A1E58008ECB67 /* MGLShapeSource_Private.h in Headers */, 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, @@ -2195,6 +2228,7 @@ 3510FFF01D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, 74CB5EBF219B280400102936 /* MGLHeatmapStyleLayer_Private.h in Headers */, 1FC4817D2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */, + 4BC5767B25E3C8AB006E06EB /* MGLObserver.h in Headers */, 1F2B94C0221636D900210640 /* MGLNetworkConfiguration_Private.h in Headers */, 353AFA141D65AB17005A69F4 /* NSDate+MGLAdditions.h in Headers */, DA8848531CBAFB9800AB86E3 /* MGLCompactCalloutView.h in Headers */, @@ -2239,6 +2273,7 @@ CAFB3C14234505D500399265 /* MGLMapSnapshotter_Private.h in Headers */, 1FCAE2A220B872A400C577DD /* MGLLocationManager.h in Headers */, DACA86262019218600E9693A /* MGLRasterDEMSource.h in Headers */, + 4BC5769325E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h in Headers */, 353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */, 92F2C3ED1F0E3C3A00268EC0 /* MGLRendererFrontend.h in Headers */, DAAF722D1DA903C700312FA4 /* MGLStyleValue_Private.h in Headers */, @@ -2300,6 +2335,7 @@ DA72620B1DEEE3480043BB89 /* MGLOpenGLStyleLayer.h in Headers */, 404C26E71D89C55D000AA13D /* MGLTileSource_Private.h in Headers */, DA88485C1CBAFB9800AB86E3 /* MGLFaux3DUserLocationAnnotationView.h in Headers */, + 4BC5767D25E3C8AB006E06EB /* MGLEvent_Private.h in Headers */, 359F57461D2FDDA6005217F1 /* MGLUserLocationAnnotationView_Private.h in Headers */, 404C26E21D89B877000AA13D /* MGLTileSource.h in Headers */, DA8847FD1CBAFA5100AB86E3 /* MGLTilePyramidOfflineRegion.h in Headers */, @@ -2308,6 +2344,7 @@ 350098BB1D480108004B2AF0 /* MGLVectorTileSource.h in Headers */, DA8847F61CBAFA5100AB86E3 /* MGLOfflineStorage.h in Headers */, DAD1656E1CF41981001FF4B9 /* MGLFeature_Private.h in Headers */, + 4BC5768525E3C8AB006E06EB /* MGLObserver_Private.h in Headers */, DA88483C1CBAFB8500AB86E3 /* MGLMapView.h in Headers */, 3EA9363147E77DD29FA06063 /* MGLRendererConfiguration.h in Headers */, 550570C822958FB400228ECF /* MGLMapView+Impl.h in Headers */, @@ -2340,6 +2377,7 @@ 3566C7671D4A77BA008152BC /* MGLShapeSource.h in Headers */, DA35A29F1CC9E94C00E826B2 /* MGLCoordinateFormatter.h in Headers */, 967C864C210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */, + 4BC5768425E3C8AB006E06EB /* MGLEvent.h in Headers */, 404C26E31D89B877000AA13D /* MGLTileSource.h in Headers */, 96E516F6200059EC00A02306 /* MGLRendererFrontend.h in Headers */, 071BBB041EE76147001FB02A /* MGLImageSource.h in Headers */, @@ -2353,6 +2391,7 @@ 96E516EF2000594F00A02306 /* NSArray+MGLAdditions.h in Headers */, 96E516F12000596800A02306 /* NSString+MGLAdditions.h in Headers */, 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */, + 4BC5767C25E3C8AB006E06EB /* MGLObserver.h in Headers */, CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */, DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */, DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */, @@ -2397,6 +2436,7 @@ 1F2B94C1221636D900210640 /* MGLNetworkConfiguration_Private.h in Headers */, 35CE61831D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */, 96E516F32000597100A02306 /* NSDictionary+MGLAdditions.h in Headers */, + 4BC5769425E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h in Headers */, 1FCAE2A920B88B3800C577DD /* MGLLocationManager_Private.h in Headers */, 96E516F02000595800A02306 /* NSBundle+MGLAdditions.h in Headers */, 96E516F920005A3500A02306 /* MGLFaux3DUserLocationAnnotationView.h in Headers */, @@ -2458,6 +2498,7 @@ 96E516E82000560B00A02306 /* MGLAnnotationContainerView_Private.h in Headers */, 35D13AC41D3D19DD00AFB4E0 /* MGLFillStyleLayer.h in Headers */, DABFB86E1CBE9A0F00D62B32 /* MGLCalloutView.h in Headers */, + 4BC5767E25E3C8AB006E06EB /* MGLEvent_Private.h in Headers */, 96E516FC20005A4400A02306 /* MGLUserLocationHeadingIndicator.h in Headers */, 1F7454971ECD450D00021D39 /* MGLLight_Private.h in Headers */, DABFB8601CBE99E500D62B32 /* MGLMapCamera.h in Headers */, @@ -2466,6 +2507,7 @@ DABFB86A1CBE99E500D62B32 /* MGLStyle.h in Headers */, DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */, 96E516E12000551100A02306 /* MGLMultiPoint_Private.h in Headers */, + 4BC5768625E3C8AB006E06EB /* MGLObserver_Private.h in Headers */, 3EA934623AD0000B7D99C3FB /* MGLRendererConfiguration.h in Headers */, DACA86272019218600E9693A /* MGLRasterDEMSource.h in Headers */, 9621F2502091020E005B3800 /* NSExpression+MGLAdditions.h in Headers */, @@ -3055,6 +3097,7 @@ DA8848261CBAFA6200AB86E3 /* MGLPolygon.mm in Sources */, 35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */, 966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */, + 4BC5768125E3C8AB006E06EB /* MGLObserver.mm in Sources */, 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */, 353AFA161D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */, 1FCAE2A420B872A400C577DD /* MGLLocationManager.m in Sources */, @@ -3062,6 +3105,7 @@ DA8848241CBAFA6200AB86E3 /* MGLOfflineStorage.mm in Sources */, DA88482A1CBAFA6200AB86E3 /* MGLTilePyramidOfflineRegion.mm in Sources */, 4049C29F1DB6CD6C00B3F799 /* MGLPointCollection.mm in Sources */, + 4BC5769525E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m in Sources */, 35136D3F1D42273000C20EFD /* MGLLineStyleLayer.mm in Sources */, DA704CC41F65A475004B3F28 /* MGLMapAccessibilityElement.mm in Sources */, DA72620D1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, @@ -3070,6 +3114,7 @@ DA8848271CBAFA6200AB86E3 /* MGLPolyline.mm in Sources */, 35CE61841D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */, 3EA93369F61CF70AFA50465D /* MGLRendererConfiguration.m in Sources */, + 4BC5767F25E3C8AB006E06EB /* MGLEvent.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3151,6 +3196,7 @@ 966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */, DAA4E4231CBB730400178DFB /* MGLPolygon.mm in Sources */, 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */, + 4BC5768225E3C8AB006E06EB /* MGLObserver.mm in Sources */, 353AFA171D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */, 35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */, 1FCAE2A520B872A400C577DD /* MGLLocationManager.m in Sources */, @@ -3158,6 +3204,7 @@ 4049C2A01DB6CD6C00B3F799 /* MGLPointCollection.mm in Sources */, 35136D401D42273000C20EFD /* MGLLineStyleLayer.mm in Sources */, DA704CC51F65A475004B3F28 /* MGLMapAccessibilityElement.mm in Sources */, + 4BC5769625E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m in Sources */, DA72620E1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, DAA4E42F1CBB730400178DFB /* MGLCompactCalloutView.m in Sources */, 3510FFFC1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */, @@ -3166,6 +3213,7 @@ 35CE61851D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */, DAA4E4241CBB730400178DFB /* MGLPolyline.mm in Sources */, 3EA9366247780E4F252652A8 /* MGLRendererConfiguration.m in Sources */, + 4BC5768025E3C8AB006E06EB /* MGLEvent.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/platform/ios/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m index 30ab7de26..fc77c659d 100644 --- a/platform/ios/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m +++ b/platform/ios/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m @@ -1,10 +1,13 @@ #import "MGLFaux3DUserLocationAnnotationView.h" #import "MGLMapView.h" +#import "MGLMapViewDelegate.h" #import "MGLUserLocation.h" #import "MGLUserLocationHeadingIndicator.h" #import "MGLUserLocationHeadingArrowLayer.h" #import "MGLUserLocationHeadingBeamLayer.h" +#import "MGLLocationManager_Private.h" +#import "MGLUserLocationAnnotationViewStyle.h" const CGFloat MGLUserLocationAnnotationDotSize = 22.0; const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; @@ -14,9 +17,12 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; +const CGFloat MGLUserLocationApproximateZoomThreshold = 7.0; + @implementation MGLFaux3DUserLocationAnnotationView { BOOL _puckModeActivated; + BOOL _approximateModeActivated; CALayer *_puckDot; CAShapeLayer *_puckArrow; @@ -27,6 +33,8 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; CALayer *_dotLayer; CALayer *_haloLayer; + CALayer *_approximateLayer; + CLLocationDirection _oldHeadingAccuracy; CLLocationAccuracy _oldHorizontalAccuracy; double _oldZoom; @@ -49,28 +57,77 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; if (CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) { - (self.mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? [self drawPuck] : [self drawDot]; - [self updatePitch]; + if (@available(iOS 14, *)) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 + if (![self.mapView.locationManager respondsToSelector:@selector(accuracyAuthorization)] || + self.mapView.locationManager.accuracyAuthorization == CLAccuracyAuthorizationFullAccuracy) { + [self drawPreciseLocationPuck]; + } else { + [self drawApproximate]; + [self updatePitch]; + } +#endif + } else { + [self drawPreciseLocationPuck]; + } + } +} +- (void)drawPreciseLocationPuck { + if (_approximateModeActivated) { + [_approximateLayer removeFromSuperlayer]; + _approximateLayer = nil; + + _approximateModeActivated = NO; + } + (self.mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? [self drawPuck] : [self drawDot]; + [self updatePitch]; _haloLayer.hidden = ! CLLocationCoordinate2DIsValid(self.mapView.userLocation.coordinate) || self.mapView.userLocation.location.horizontalAccuracy > 10; } + - (void)setTintColor:(UIColor *)tintColor { - CGColorRef newTintColor = [tintColor CGColor]; - + UIColor *puckArrowFillColor = tintColor; + UIColor *puckArrowStrokeColor = tintColor; + + UIColor *approximateFillColor = tintColor; + + UIColor *accuracyFillColor = tintColor; + UIColor *haloFillColor = tintColor; + UIColor *dotFillColor = tintColor; + UIColor *headingFillColor = tintColor; + + if ([self.mapView.delegate respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { + MGLUserLocationAnnotationViewStyle *style = [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; + + puckArrowFillColor = style.puckArrowFillColor ? style.puckArrowFillColor : puckArrowFillColor; + + if (@available(iOS 14, *)) { + approximateFillColor = style.approximateHaloFillColor ? style.approximateHaloFillColor : approximateFillColor; + } + + haloFillColor = style.haloFillColor ? style.haloFillColor : haloFillColor; + dotFillColor = style.puckFillColor ? style.puckFillColor : dotFillColor; + headingFillColor = style.puckFillColor ? style.puckFillColor : headingFillColor; + } + if (_puckModeActivated) { - _puckArrow.fillColor = newTintColor; - _puckArrow.strokeColor = newTintColor; + _puckArrow.fillColor = [puckArrowFillColor CGColor]; + _puckArrow.strokeColor = [puckArrowStrokeColor CGColor]; + } + else if (_approximateModeActivated) + { + _approximateLayer.backgroundColor = [approximateFillColor CGColor]; } else { - _accuracyRingLayer.backgroundColor = newTintColor; - _haloLayer.backgroundColor = newTintColor; - _dotLayer.backgroundColor = newTintColor; - [_headingIndicatorLayer updateTintColor:newTintColor]; + _accuracyRingLayer.backgroundColor = [accuracyFillColor CGColor]; + _haloLayer.backgroundColor = [haloFillColor CGColor]; + _dotLayer.backgroundColor = [dotFillColor CGColor]; + [_headingIndicatorLayer updateTintColor:[headingFillColor CGColor]]; } } @@ -146,14 +203,26 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; [self updateFrameWithSize:MGLUserLocationAnnotationPuckSize]; } + UIColor *arrowColor = self.mapView.tintColor; + UIColor *puckShadowColor = UIColor.blackColor; + CGFloat shadowOpacity = 0.25; + + + if ([self.mapView.delegate respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { + MGLUserLocationAnnotationViewStyle *style = [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; + arrowColor = style.puckArrowFillColor ? style.puckArrowFillColor : arrowColor; + puckShadowColor = style.puckShadowColor ? style.puckShadowColor : puckShadowColor; + shadowOpacity = style.puckShadowOpacity; + } + // background dot (white with black shadow) // if ( ! _puckDot) { _puckDot = [self circleLayerWithSize:MGLUserLocationAnnotationPuckSize]; _puckDot.backgroundColor = [[UIColor whiteColor] CGColor]; - _puckDot.shadowColor = [[UIColor blackColor] CGColor]; - _puckDot.shadowOpacity = 0.25; + _puckDot.shadowColor = [puckShadowColor CGColor]; + _puckDot.shadowOpacity = shadowOpacity; _puckDot.shadowPath = [[UIBezierPath bezierPathWithOvalInRect:_puckDot.bounds] CGPath]; if (self.mapView.camera.pitch) @@ -175,7 +244,7 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; { _puckArrow = [CAShapeLayer layer]; _puckArrow.path = [[self puckArrow] CGPath]; - _puckArrow.fillColor = [self.mapView.tintColor CGColor]; + _puckArrow.fillColor = [arrowColor CGColor]; _puckArrow.bounds = CGRectMake(0, 0, round(MGLUserLocationAnnotationArrowSize), round(MGLUserLocationAnnotationArrowSize)); _puckArrow.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds)); _puckArrow.shouldRasterize = YES; @@ -228,6 +297,20 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; [self updateFrameWithSize:MGLUserLocationAnnotationDotSize]; } + + UIColor *haloColor = self.mapView.tintColor; + UIColor *puckBackgroundColor = self.mapView.tintColor; + UIColor *puckShadowColor = UIColor.blackColor; + CGFloat shadowOpacity = 0.25; + + if ([self.mapView.delegate respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { + MGLUserLocationAnnotationViewStyle *style = [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; + haloColor = style.haloFillColor ? style.haloFillColor : haloColor; + puckBackgroundColor = style.puckFillColor ? style.puckFillColor : puckBackgroundColor; + puckShadowColor = style.puckShadowColor ? style.puckShadowColor : puckShadowColor; + shadowOpacity = style.puckShadowOpacity; + } + // heading indicator (tinted, beam or arrow) // BOOL headingTrackingModeEnabled = self.mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading; @@ -251,11 +334,13 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; if (headingTrackingModeEnabled) { _headingIndicatorLayer = [[MGLUserLocationHeadingBeamLayer alloc] initWithUserLocationAnnotationView:self]; + [_headingIndicatorLayer updateTintColor:[haloColor CGColor]]; [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer]; } else { _headingIndicatorLayer = [[MGLUserLocationHeadingArrowLayer alloc] initWithUserLocationAnnotationView:self]; + [_headingIndicatorLayer updateTintColor:[puckBackgroundColor CGColor]]; [self.layer addSublayer:_headingIndicatorLayer]; _headingIndicatorLayer.zPosition = 1; } @@ -351,7 +436,7 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; if ( ! _haloLayer) { _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize]; - _haloLayer.backgroundColor = [self.mapView.tintColor CGColor]; + _haloLayer.backgroundColor = [haloColor CGColor]; _haloLayer.allowsGroupOpacity = NO; _haloLayer.zPosition = -0.1f; @@ -381,8 +466,8 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; { _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize]; _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; - _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor]; - _dotBorderLayer.shadowOpacity = 0.25; + _dotBorderLayer.shadowColor = [puckShadowColor CGColor]; + _dotBorderLayer.shadowOpacity = shadowOpacity; _dotBorderLayer.shadowPath = [[UIBezierPath bezierPathWithOvalInRect:_dotBorderLayer.bounds] CGPath]; if (self.mapView.camera.pitch) @@ -403,7 +488,7 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; if ( ! _dotLayer) { _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75]; - _dotLayer.backgroundColor = [self.mapView.tintColor CGColor]; + _dotLayer.backgroundColor = [puckBackgroundColor CGColor]; // set defaults for the animations CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5]; @@ -435,6 +520,85 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; } } +- (void)drawApproximate +{ + if ( ! _approximateModeActivated) + { + self.layer.sublayers = nil; + + _headingIndicatorLayer = nil; + _dotBorderLayer = nil; + _dotLayer = nil; + _accuracyRingLayer = nil; + _haloLayer = nil; + _puckDot = nil; + _puckArrow = nil; + + _approximateModeActivated = YES; + } + + UIColor *backgroundColor = self.mapView.tintColor; + UIColor *strokeColor = UIColor.blackColor; + CGFloat borderSize = 2.0; + CGFloat opacity = 0.25; + + if ([self.mapView.delegate respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { + MGLUserLocationAnnotationViewStyle *style = [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; + if (@available(iOS 14, *)) { + backgroundColor = style.approximateHaloFillColor ? style.approximateHaloFillColor : backgroundColor; + strokeColor = style.approximateHaloBorderColor ? style.approximateHaloBorderColor : strokeColor; + opacity = style.approximateHaloOpacity; + borderSize = style.approximateHaloBorderWidth; + } + } + + // update approximate ring (if zoom or horizontal accuracy have changed) + if (_approximateLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.userLocation.location.horizontalAccuracy)) + { + CGFloat borderWidth = 2; + if (self.mapView.zoomLevel < MGLUserLocationApproximateZoomThreshold) { + borderSize = 3; + } + _approximateLayer.borderWidth = borderSize; + + if (self.mapView.zoomLevel >= MGLUserLocationApproximateZoomThreshold) { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + + _approximateLayer.hidden = NO; + + // disable implicit animation of the accuracy ring, unless triggered by a change in accuracy + BOOL shouldDisableActions = _oldHorizontalAccuracy == self.userLocation.location.horizontalAccuracy; + + [CATransaction begin]; + [CATransaction setDisableActions:shouldDisableActions]; + + _approximateLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); + _approximateLayer.cornerRadius = accuracyRingSize / 2.0; + + [CATransaction commit]; + } + + // store accuracy and zoom so we're not redrawing unchanged location updates + _oldHorizontalAccuracy = self.userLocation.location.horizontalAccuracy; + _oldZoom = self.mapView.zoomLevel; + } + + // approximate ring + if ( ! _approximateLayer && self.userLocation.location.horizontalAccuracy) + { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + _approximateLayer = [self circleLayerWithSize:accuracyRingSize]; + approximateLayer.backgroundColor = [backgroundColor CGColor]; + _approximateLayer.opacity = opacity; + _approximateLayer.shouldRasterize = NO; + _approximateLayer.allowsGroupOpacity = NO; + _approximateLayer.borderWidth = borderSize; + _approximateLayer.borderColor = [strokeColor CGColor]; + + [self.layer addSublayer:_approximateLayer]; + } +} + - (CALayer *)circleLayerWithSize:(CGFloat)layerSize { layerSize = round(layerSize); diff --git a/platform/ios/platform/ios/src/MGLMapView.h b/platform/ios/platform/ios/src/MGLMapView.h index f2eb51dd1..a6c6ea3da 100644 --- a/platform/ios/platform/ios/src/MGLMapView.h +++ b/platform/ios/platform/ios/src/MGLMapView.h @@ -6,6 +6,7 @@ #import "MGLMapCamera.h" #import "MGLTypes.h" #import "MGLStyle.h" +#import "MGLObserver.h" NS_ASSUME_NONNULL_BEGIN @@ -188,7 +189,7 @@ FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const MGLUserLocationAnnotationTyp Simple map view example to learn how to initialize a basic `MGLMapView`. */ MGL_EXPORT -@interface MGLMapView : UIView +@interface MGLMapView : UIView #pragma mark Creating Instances @@ -2002,6 +2003,14 @@ MGL_EXPORT */ @property (nonatomic) MGLMapDebugMaskOptions debugMask; +/** + :nodoc: + Convenience method for subscribing to a single event. See `-[MGLObservable + subscribeForObserver:events:]`. + */ +- (void)subscribeForObserver:(nonnull MGLObserver *)observer + event:(nonnull MGLEventType)event; + @end NS_ASSUME_NONNULL_END diff --git a/platform/ios/platform/ios/src/MGLMapView.mm b/platform/ios/platform/ios/src/MGLMapView.mm index f16883b22..8890b11fb 100644 --- a/platform/ios/platform/ios/src/MGLMapView.mm +++ b/platform/ios/platform/ios/src/MGLMapView.mm @@ -67,6 +67,8 @@ #import "MGLLoggingConfiguration_Private.h" #import "MGLNetworkConfiguration_Private.h" #import "MGLReachability.h" +#import "MGLObserver_Private.h" +#import "MGLEvent_Private.h" #include #include @@ -277,6 +279,8 @@ public: @property (nonatomic, assign) UIEdgeInsets safeMapViewContentInsets; @property (nonatomic, strong) NSNumber *automaticallyAdjustContentInsetHolder; +@property (nonatomic) NSMutableSet *observerCache; + @end @implementation MGLMapView @@ -517,6 +521,8 @@ public: _annotationViewReuseQueueByIdentifier = [NSMutableDictionary dictionary]; _selectedAnnotationTag = MGLAnnotationTagNotFound; _annotationsNearbyLastTap = {}; + + _observerCache = [NSMutableSet set]; // TODO: This warning should be removed when automaticallyAdjustsScrollViewInsets is removed from // the UIViewController api. @@ -5417,6 +5423,11 @@ public: } } +- (NSString *)accuracyDescriptionString { + NSDictionary *dictionary = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationTemporaryUsageDescriptionDictionary"]; + return dictionary[@"MGLAccuracyAuthorizationDescription"]; +} + - (void)setShowsUserLocation:(BOOL)showsUserLocation { MGLLogDebug(@"Setting showsUserLocation: %@", MGLStringFromBOOL(showsUserLocation)); @@ -5975,6 +5986,47 @@ public: } } +- (void)locationManagerDidChangeAuthorization:(id)manager +{ + if (![self shouldShowLocationDotBasedOnCurrentLocationPermissions]) + { + [self.userLocationAnnotationView removeFromSuperview]; + [self.locationManager stopUpdatingLocation]; + [self.locationManager stopUpdatingHeading]; + } else { + if (@available(iOS 14, *)) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 + if (self.userTrackingMode != MGLUserTrackingModeNone && + [manager respondsToSelector:@selector(authorizationStatus)] && + (manager.authorizationStatus != kCLAuthorizationStatusRestricted || + manager.authorizationStatus != kCLAuthorizationStatusAuthorizedAlways || + manager.authorizationStatus != kCLAuthorizationStatusAuthorizedWhenInUse) && + [manager respondsToSelector:@selector(accuracyAuthorization)] && + manager.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy && + [self accuracyDescriptionString] != nil ) { + [self.locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:@"MGLAccuracyAuthorizationDescription"]; + } else { + [self validateLocationServices]; + } +#endif + } else { + [self validateLocationServices]; + } + } + + if (@available(iOS 14, *)) { + if ([self.delegate respondsToSelector:@selector(mapView:didChangeLocationManagerAuthorization:)]) { + [self.delegate mapView:self didChangeLocationManagerAuthorization:manager]; + } + } +} + +- (BOOL)shouldShowLocationDotBasedOnCurrentLocationPermissions +{ + return self.locationManager && (self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways + || self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse); +} + - (void)updateHeadingForDeviceOrientation { if (self.locationManager) @@ -6840,6 +6892,41 @@ public: return _annotationViewReuseQueueByIdentifier[identifier]; } +#pragma mark - MGLObservable methods - + +static std::vector vectorOfStringsFromSet(NSSet *setOfStrings) { + __block std::vector strings; + strings.reserve(setOfStrings.count); + [setOfStrings enumerateObjectsUsingBlock:^(NSString * _Nonnull text, BOOL * _Nonnull stop) { + strings.push_back(text.UTF8String); + }]; + return strings; +} + +// Convenience method +- (void)subscribeForObserver:(nonnull MGLObserver *)observer event:(nonnull MGLEventType)event { + [self subscribeForObserver:observer events:[NSSet setWithObject:event]]; +} + +- (void)subscribeForObserver:(MGLObserver *)observer events:(nonnull NSSet *)events { + observer.observing = YES; + [self.observerCache addObject:observer]; + + auto eventTypes = vectorOfStringsFromSet(events); + self.mbglMap.subscribe(observer.peer, eventTypes); +} + +- (void)unsubscribeForObserver:(MGLObserver *)observer events:(nonnull NSSet *)events { + auto eventTypes = vectorOfStringsFromSet(events); + self.mbglMap.unsubscribe(observer.peer, eventTypes); +} + +- (void)unsubscribeForObserver:(MGLObserver *)observer { + self.mbglMap.unsubscribe(observer.peer); + [self.observerCache removeObject:observer]; + observer.observing = NO; +} + @end #pragma mark - IBAdditions methods diff --git a/platform/ios/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/platform/ios/src/MGLMapViewDelegate.h index 3ddb7b007..e3ba90e05 100644 --- a/platform/ios/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/platform/ios/src/MGLMapViewDelegate.h @@ -6,6 +6,7 @@ NS_ASSUME_NONNULL_BEGIN @class MGLMapView; +@class MGLUserLocationAnnotationViewStyle; /** The `MGLMapViewDelegate` protocol defines a set of optional methods that you @@ -313,6 +314,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)mapViewDidStopLocatingUser:(MGLMapView *)mapView; +/** + Asks the delegate styling options for each default user location annotation view. + + This method is called many times during gesturing, so you should avoid performing + complex or performance-intensive tasks in your implementation. + + @param mapView The map view that is tracking the user’s location. + */ +- (MGLUserLocationAnnotationViewStyle *)mapViewStyleForDefaultUserLocationAnnotationView:(MGLMapView *)mapView NS_SWIFT_NAME(mapView(styleForDefaultUserLocationAnnotationView:)); + /** Tells the delegate that the location of the user was updated. @@ -368,6 +379,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (CGPoint)mapViewUserLocationAnchorPoint:(MGLMapView *)mapView; +/** + Tells the delegate that the map's location updates accuracy authorization has changed. + + This method is called after the user changes location accuracy authorization when + requesting location permissions or in privacy settings. + + @param mapView The map view that changed its location accuracy authorization. + @param manager The location manager reporting the update. + + */ +- (void)mapView:(MGLMapView *)mapView didChangeLocationManagerAuthorization:(id)manager API_AVAILABLE(ios(14)); + #pragma mark Managing the Appearance of Annotations /** diff --git a/platform/ios/platform/ios/src/MGLUserLocationAnnotationViewStyle.h b/platform/ios/platform/ios/src/MGLUserLocationAnnotationViewStyle.h new file mode 100644 index 000000000..26c580a4c --- /dev/null +++ b/platform/ios/platform/ios/src/MGLUserLocationAnnotationViewStyle.h @@ -0,0 +1,57 @@ +#import + +#import "MGLFoundation.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + A class containing information about the default User Location annotation view style. + */ +MGL_EXPORT +@interface MGLUserLocationAnnotationViewStyle : NSObject + +/** + The fill color for the puck view. + */ +@property (nonatomic) UIColor *puckFillColor; +/** + The shadow color for the puck view. + */ +@property (nonatomic) UIColor *puckShadowColor; +/** + The shadow opacity for the puck view. + Set any value between 0.0 and 1.0. + The default value of this property is equal to `0.25` + */ +@property (nonatomic, assign) CGFloat puckShadowOpacity; +/** + The fill color for the arrow puck. + */ +@property (nonatomic) UIColor *puckArrowFillColor; +/** + The fill color for the puck view. + */ +@property (nonatomic) UIColor *haloFillColor; +/** + The halo fill color for the approximate view. + */ +@property (nonatomic) UIColor *approximateHaloFillColor API_AVAILABLE(ios(14)); +/** + The halo border color for the approximate view. + */ +@property (nonatomic) UIColor *approximateHaloBorderColor API_AVAILABLE(ios(14)); +/** + The halo border width for the approximate view. + The default value of this property is equal to `2.0` + */ +@property (nonatomic, assign) CGFloat approximateHaloBorderWidth API_AVAILABLE(ios(14)); +/** + The halo opacity for the approximate view. + Set any value between 0.0 and 1.0 + The default value of this property is equal to `0.15` + */ +@property (nonatomic, assign) CGFloat approximateHaloOpacity API_AVAILABLE(ios(14)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/ios/platform/ios/src/MGLUserLocationAnnotationViewStyle.m b/platform/ios/platform/ios/src/MGLUserLocationAnnotationViewStyle.m new file mode 100644 index 000000000..3857a9c9e --- /dev/null +++ b/platform/ios/platform/ios/src/MGLUserLocationAnnotationViewStyle.m @@ -0,0 +1,62 @@ +#import "MGLUserLocationAnnotationViewStyle.h" +#import "MGLLoggingConfiguration_Private.h" + +@implementation MGLUserLocationAnnotationViewStyle + +- (instancetype)init { + if ((self = [super init])) { + self.puckShadowOpacity = 0.25; + if (@available(iOS 14, *)) { + self.approximateHaloBorderWidth = 2.0; + self.approximateHaloOpacity = 0.15; + } + } + return self; +} + +- (void)setPuckFillColor:(UIColor *)puckFillColor { + MGLLogDebug(@"Setting puckFillColor: %@", puckFillColor); + _puckFillColor = puckFillColor; +} + +- (void)setPuckShadowColor:(UIColor *)puckShadowColor { + MGLLogDebug(@"Setting puckShadowColor: %@", puckShadowColor); + _puckShadowColor = puckShadowColor; +} + +- (void)setPuckShadowOpacity:(CGFloat)puckShadowOpacity { + MGLLogDebug(@"Setting puckShadowOpacity: %.2f", puckShadowOpacity); + _puckShadowOpacity = puckShadowOpacity; +} + +- (void)setPuckArrowFillColor:(UIColor *)puckArrowFillColor { + MGLLogDebug(@"Setting puckArrowFillColor: %@", puckArrowFillColor); + _puckArrowFillColor = puckArrowFillColor; +} + +- (void)setHaloFillColor:(UIColor *)haloFillColor { + MGLLogDebug(@"Setting haloFillColor: %@", haloFillColor); + _haloFillColor = haloFillColor; +} + +- (void)setApproximateHaloFillColor:(UIColor *)approximateHaloFillColor { + MGLLogDebug(@"Setting approximateHaloFillColor: %@", approximateHaloFillColor); + _approximateHaloFillColor = approximateHaloFillColor; +} + +- (void)setApproximateHaloBorderColor:(UIColor *)approximateHaloBorderColor { + MGLLogDebug(@"Setting approximateHaloBorderColor: %@", approximateHaloBorderColor); + _approximateHaloBorderColor = approximateHaloBorderColor; +} + +- (void)setApproximateHaloBorderWidth:(CGFloat)approximateHaloBorderWidth { + MGLLogDebug(@"Setting approximateHaloBorderWidth: %.2f", approximateHaloBorderWidth); + _approximateHaloBorderWidth = approximateHaloBorderWidth; +} + +- (void)setApproximateHaloOpacity:(CGFloat)approximateHaloOpacity { + MGLLogDebug(@"Setting approximateHaloOpacity: %.2f", approximateHaloOpacity); + _approximateHaloOpacity = approximateHaloOpacity; +} + +@end \ No newline at end of file diff --git a/platform/ios/platform/ios/src/Mapbox.h b/platform/ios/platform/ios/src/Mapbox.h index b5fd86e85..88e0486a8 100644 --- a/platform/ios/platform/ios/src/Mapbox.h +++ b/platform/ios/platform/ios/src/Mapbox.h @@ -1,5 +1,4 @@ #import - #import "MGLFoundation.h" /// Project version number for Mapbox. @@ -8,69 +7,73 @@ FOUNDATION_EXPORT MGL_EXPORT double MapboxVersionNumber; /// Project version string for Mapbox. FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; -#import "MGLAnnotationView.h" #import "MGLAccountManager.h" #import "MGLAnnotation.h" #import "MGLAnnotationImage.h" +#import "MGLAnnotationView.h" +#import "MGLAttributedExpression.h" +#import "MGLAttributionInfo.h" +#import "MGLBackgroundStyleLayer.h" #import "MGLCalloutView.h" +#import "MGLCircleStyleLayer.h" #import "MGLClockDirectionFormatter.h" #import "MGLCluster.h" #import "MGLCompassButton.h" #import "MGLCompassDirectionFormatter.h" +#import "MGLComputedShapeSource.h" #import "MGLCoordinateFormatter.h" #import "MGLDistanceFormatter.h" +#import "MGLEvent.h" #import "MGLFeature.h" +#import "MGLFillExtrusionStyleLayer.h" +#import "MGLFillStyleLayer.h" +#import "MGLForegroundStyleLayer.h" #import "MGLGeometry.h" +#import "MGLHeatmapStyleLayer.h" +#import "MGLHillshadeStyleLayer.h" +#import "MGLImageSource.h" #import "MGLLight.h" +#import "MGLLineStyleLayer.h" +#import "MGLLocationManager.h" +#import "MGLLoggingConfiguration.h" #import "MGLMapCamera.h" +#import "MGLMapSnapshotter.h" #import "MGLMapView.h" -#import "MGLMapView+IBAdditions.h" #import "MGLMapViewDelegate.h" +#import "MGLMapView+IBAdditions.h" #import "MGLMultiPoint.h" +#import "MGLNetworkConfiguration.h" +#import "MGLObserver.h" #import "MGLOfflinePack.h" #import "MGLOfflineRegion.h" #import "MGLOfflineStorage.h" +#import "MGLOpenGLStyleLayer.h" #import "MGLOverlay.h" #import "MGLPointAnnotation.h" #import "MGLPointCollection.h" #import "MGLPolygon.h" #import "MGLPolyline.h" +#import "MGLRasterDEMSource.h" +#import "MGLRasterStyleLayer.h" +#import "MGLRasterTileSource.h" +#import "MGLSDKMetricsManager.h" #import "MGLShape.h" #import "MGLShapeCollection.h" +#import "MGLShapeOfflineRegion.h" +#import "MGLShapeSource.h" +#import "MGLSource.h" #import "MGLStyle.h" #import "MGLStyleLayer.h" -#import "MGLForegroundStyleLayer.h" -#import "MGLVectorStyleLayer.h" -#import "MGLFillExtrusionStyleLayer.h" -#import "MGLFillStyleLayer.h" -#import "MGLLineStyleLayer.h" +#import "MGLStyleValue.h" #import "MGLSymbolStyleLayer.h" -#import "MGLRasterStyleLayer.h" -#import "MGLCircleStyleLayer.h" -#import "MGLHeatmapStyleLayer.h" -#import "MGLHillshadeStyleLayer.h" -#import "MGLBackgroundStyleLayer.h" -#import "MGLOpenGLStyleLayer.h" -#import "MGLSource.h" -#import "MGLTileSource.h" -#import "MGLVectorTileSource.h" -#import "MGLShapeSource.h" -#import "MGLComputedShapeSource.h" -#import "MGLRasterTileSource.h" -#import "MGLRasterDEMSource.h" -#import "MGLImageSource.h" -#import "MGLShapeOfflineRegion.h" #import "MGLTilePyramidOfflineRegion.h" +#import "MGLTileSource.h" #import "MGLTypes.h" #import "MGLUserLocation.h" #import "MGLUserLocationAnnotationView.h" -#import "NSValue+MGLAdditions.h" -#import "MGLStyleValue.h" -#import "MGLAttributionInfo.h" -#import "MGLMapSnapshotter.h" +#import "MGLVectorStyleLayer.h" +#import "MGLVectorTileSource.h" #import "NSExpression+MGLAdditions.h" #import "NSPredicate+MGLAdditions.h" -#import "MGLLocationManager.h" -#import "MGLLoggingConfiguration.h" -#import "MGLNetworkConfiguration.h" -#import "MGLAttributedExpression.h" +#import "NSValue+MGLAdditions.h" +#import "MGLUserLocationAnnotationViewStyle.h" \ No newline at end of file diff --git a/platform/ios/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift index 172538c65..d581183c7 100644 --- a/platform/ios/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/ios/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift @@ -100,4 +100,8 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, didFailToLoadImage imageName: String) -> UIImage? { return nil } func mapView(_ mapView: MGLMapView, shouldRemoveStyleImage imageName: String) -> Bool { return false } + + func mapView(_ mapView: MGLMapView, didChangeLocationManagerAuthorization manager: MGLLocationManager) {} + + func mapView(styleForDefaultUserLocationAnnotationView mapView: MGLMapView) -> MGLUserLocationAnnotationViewStyle { return MGLUserLocationAnnotationViewStyle() } }