diff options
author | Fabian Guerra <fabian.guerra@mapbox.com> | 2017-09-27 12:08:24 -0400 |
---|---|---|
committer | Fabian Guerra <fabian.guerra@mapbox.com> | 2017-09-27 12:08:24 -0400 |
commit | 6d1fdf2ca3a93839bc7dc0929599dd096ab00e33 (patch) | |
tree | 5436aefbab4abcba5caa5020c62c24de7d493616 /platform/ios/src/MGLMapView.mm | |
parent | fafe0928227ec8e944e1ec68ee1d2bbc1c52f6ae (diff) | |
parent | 3f52377543885170da6a27e1c24657cc4cbcf29c (diff) | |
download | qtlocation-mapboxgl-6d1fdf2ca3a93839bc7dc0929599dd096ab00e33.tar.gz |
Merge branch 'release-ios-v3.6.0-android-v5.1.0' into master.upstream/fabian-merge-release-ios-v3.6.0-final
# Conflicts:
# circle.yml
# platform/android/CHANGELOG.md
# platform/android/MapboxGLAndroidSDK/gradle.properties
# platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java
# platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
# platform/android/dependencies.gradle
# platform/default/mbgl/storage/offline_database.cpp
# platform/ios/CHANGELOG.md
# platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec
# platform/ios/Mapbox-iOS-SDK-symbols.podspec
# platform/ios/Mapbox-iOS-SDK.podspec
# platform/ios/app/Main.storyboard
# platform/ios/src/MGLMapView.mm
# src/mbgl/programs/attributes.hpp
# src/mbgl/renderer/painter_fill.cpp
# src/mbgl/tile/geometry_tile.hpp
# src/mbgl/tile/geometry_tile_worker.cpp
# src/mbgl/tile/raster_tile.cpp
# src/mbgl/tile/raster_tile.hpp
# src/mbgl/tile/raster_tile_worker.cpp
# test/tile/annotation_tile.test.cpp
# test/tile/geojson_tile.test.cpp
# test/tile/raster_tile.test.cpp
# test/tile/vector_tile.test.cpp
Diffstat (limited to 'platform/ios/src/MGLMapView.mm')
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 471 |
1 files changed, 338 insertions, 133 deletions
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 |