diff --git a/platform/darwin/src/MGLLocationManager.h b/platform/darwin/src/MGLLocationManager.h new file mode 100644 index 000000000..121dcc229 --- /dev/null +++ b/platform/darwin/src/MGLLocationManager.h @@ -0,0 +1,181 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol MGLLocationManagerDelegate; + +/** + The `MGLLocationManager` protocol defines a set of methods that a class must + implement in order to serve as the location manager of an `MGLMapView`. A location + manager is responsible for notifying the map view about location-related events, + such as a change in the user’s location. This protocol is similar to the + Core Location framework’s `CLLocationManager` class, but your implementation + does not need to be based on `CLLocationManager`. + + */ +@protocol MGLLocationManager + +@optional + +#pragma mark Configuring Location Update Precision + +/** + Specifies the minimum distance (measured in meters) a device must move horizontally + before a location update is generated. + + The default value of this property is `kCLDistanceFilterNone` when `MGLMapView` uses its + default location manager. + + @see `CLLocationManager.distanceFilter` + */ +@property(nonatomic, assign) CLLocationDistance distanceFilter; + +/** + Specifies the accuracy of the location data. + + The default value is `kCLLocationAccuracyBest` when `MGLMapView` uses its + default location manager. + + @note Determining a location with greater accuracy requires more time and more power. + + @see `CLLocationManager.desiredAccuracy` + */ +@property (nonatomic, assign) CLLocationAccuracy desiredAccuracy; + +/** + Specifies the type of user activity associated with the location updates. + + The location manager uses this property as a cue to determine when location updates + may be automatically paused. + + The default value is `CLActivityTypeOther` when `MGLMapView` uses its + default location manager. + + @see `CLLocationManager.activityType` + */ +@property (nonatomic, assign) CLActivityType activityType; + +@required + +/** + The delegate to receive location updates. + + Do not set the location manager’s delegate yourself. `MGLMapView` sets this property + after the location manager becomes `MGLMapView`’s location manager. + */ +@property (nonatomic, weak) id delegate; + +#pragma mark Requesting Authorization for Location Services + +/** + Returns the current localization authorization status. + + @see `+[CLLocationManger authorizationStatus]` + */ +@property (nonatomic, readonly) CLAuthorizationStatus authorizationStatus; + +/** + Requests permission to use the location services whenever the app is running. + */ +- (void)requestAlwaysAuthorization; + +/** + Requests permission to use the location services while the app is in + the foreground. + */ +- (void)requestWhenInUseAuthorization; + +#pragma mark Initiating Location Updates + +/** + Starts the generation of location updates that reports the user's current location. + */ +- (void)startUpdatingLocation; + +/** + Stops the generation of location updates. + */ +- (void)stopUpdatingLocation; + +#pragma mark Initiating Heading Updates + +/** + Specifies a physical device orientation. + */ +@property (nonatomic) CLDeviceOrientation headingOrientation; + +/** + Starts the generation of heading updates that reports the user's current hading. + */ +- (void)startUpdatingHeading; + +/** + Stops the generation of heading updates. + */ +- (void)stopUpdatingHeading; + +/** + Dissmisses immediately the heading calibration view from screen. + */ +- (void)dismissHeadingCalibrationDisplay; + +@end + +/** + The `MGLLocationManagerDelegate` protocol defines a set of methods that respond + to location updates from an `MGLLocationManager` object that is serving as the + location manager of an `MGLMapView`. + */ +@protocol MGLLocationManagerDelegate + +#pragma mark Responding to Location Updates + +/** + Notifies the delegate with the new location data. + + @param manager The location manager reporting the update. + @param locations An array of `CLLocation` objects in chronological order, + with the last object representing the most recent location. This array + contains multiple `CLLocation` objects when `MGLMapView` uses its + default location manager. + */ +- (void)locationManager:(id)manager + didUpdateLocations:(NSArray *)locations; + +#pragma mark Responding to Heading Updates + +/** + Notifies the delegate with the new heading data. + + @param manager The location manager reporting the update. + @param newHeading The new heading update. + */ +- (void)locationManager:(id)manager + didUpdateHeading:(CLHeading *)newHeading; + +/** + Asks the delegate if the calibration alert should be displayed. + + @param manager The location manager reporting the calibration. + */ +- (BOOL)locationManagerShouldDisplayHeadingCalibration:(id)manager; + +#pragma mark Responding to Location Updates Errors + +/** + Notifies the delegate that the location manager was unable to retrieve + location updates. + + @param manager The location manager reporting the error. + @param error An error object containing the error code that indicates + why the location manager failed. + */ +- (void)locationManager:(id)manager + didFailWithError:(nonnull NSError *)error; + +@optional + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLLocationManager.m b/platform/darwin/src/MGLLocationManager.m new file mode 100644 index 000000000..29e3ccaa3 --- /dev/null +++ b/platform/darwin/src/MGLLocationManager.m @@ -0,0 +1,116 @@ +#import "MGLLocationManager_Private.h" + +@interface MGLCLLocationManager() + +@property (nonatomic) CLLocationManager *locationManager; + +@end + +@implementation MGLCLLocationManager + +- (instancetype)init +{ + if (self = [super init]) { + _locationManager = [[CLLocationManager alloc] init]; + _locationManager.delegate = self; + } + return self; +} + +@synthesize delegate; + +- (void)setHeadingOrientation:(CLDeviceOrientation)headingOrientation +{ + self.locationManager.headingOrientation = headingOrientation; +} + +- (CLDeviceOrientation)headingOrientation +{ + return self.locationManager.headingOrientation; +} + +- (void)setDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy { + self.locationManager.desiredAccuracy = desiredAccuracy; +} + +- (CLLocationAccuracy)desiredAccuracy { + return self.locationManager.desiredAccuracy; +} + +- (CLAuthorizationStatus)authorizationStatus { + return [CLLocationManager authorizationStatus]; +} + +- (void)setActivityType:(CLActivityType)activityType { + self.locationManager.activityType = activityType; +} + +- (CLActivityType)activityType { + return self.locationManager.activityType; +} + +- (void)dismissHeadingCalibrationDisplay { + [self.locationManager dismissHeadingCalibrationDisplay]; +} + +- (void)requestAlwaysAuthorization { + [self.locationManager requestAlwaysAuthorization]; +} + +- (void)requestWhenInUseAuthorization { + [self.locationManager requestWhenInUseAuthorization]; +} + +- (void)startUpdatingHeading { + [self.locationManager startUpdatingHeading]; +} + +- (void)startUpdatingLocation { + [self.locationManager startUpdatingLocation]; +} + +- (void)stopUpdatingHeading { + [self.locationManager stopUpdatingHeading]; +} + +- (void)stopUpdatingLocation { + [self.locationManager stopUpdatingLocation]; +} + +- (void)dealloc +{ + [self.locationManager stopUpdatingLocation]; + [self.locationManager stopUpdatingHeading]; + self.locationManager.delegate = nil; + self.delegate = nil; +} + +#pragma mark - CLLocationManagerDelegate + +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) { + [self.delegate locationManager:self didUpdateLocations:locations]; + } +} + +- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading { + if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateHeading:)]) { + [self.delegate locationManager:self didUpdateHeading:newHeading]; + } +} + +- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager { + if ([self.delegate respondsToSelector:@selector(locationManagerShouldDisplayHeadingCalibration:)]) { + return [self.delegate locationManagerShouldDisplayHeadingCalibration:self]; + } + + return NO; +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + if ([self.delegate respondsToSelector:@selector(locationManager:didFailWithError:)]) { + [self.delegate locationManager:self didFailWithError:error]; + } +} + +@end diff --git a/platform/darwin/src/MGLLocationManager_Private.h b/platform/darwin/src/MGLLocationManager_Private.h new file mode 100644 index 000000000..4f09405e7 --- /dev/null +++ b/platform/darwin/src/MGLLocationManager_Private.h @@ -0,0 +1,5 @@ +#import "MGLLocationManager.h" + +@interface MGLCLLocationManager : NSObject + +@end diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 791b31d0a..9cc953bd1 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,8 +4,12 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ## master +### Other changes + * Fixed a crash that occurred when the user started a gesture before the drift animation for a previous gesture was complete. ([#12148](https://github.com/mapbox/mapbox-gl-native/pull/12148)) * Token string syntax (`"{token}"`) in `MGLSymbolStyleLayer` `text` and `iconImageName` properties is now correctly converted to the appropriate `NSExpression` equivalent. ([#11659](https://github.com/mapbox/mapbox-gl-native/issues/11659)) +* Added an `MGLMapView.locationManager` property and `MGLLocationManager` protocol for tracking user location using a custom alternative to `CLLocationManager`. ([#12013](https://github.com/mapbox/mapbox-gl-native/pull/12013)) + ## 4.2.0 - July 18, 2018 diff --git a/platform/ios/app/MBXCustomLocationViewController.h b/platform/ios/app/MBXCustomLocationViewController.h new file mode 100644 index 000000000..ae6c14fe2 --- /dev/null +++ b/platform/ios/app/MBXCustomLocationViewController.h @@ -0,0 +1,5 @@ +#import + +@interface MBXCustomLocationViewController : UIViewController + +@end diff --git a/platform/ios/app/MBXCustomLocationViewController.m b/platform/ios/app/MBXCustomLocationViewController.m new file mode 100644 index 000000000..34887c573 --- /dev/null +++ b/platform/ios/app/MBXCustomLocationViewController.m @@ -0,0 +1,174 @@ +#import "MBXCustomLocationViewController.h" + +#import + +@interface MBXCustomLocationManager : NSObject +@end + +@interface MBXCustomLocationManager() + +@property (nonatomic) CLLocationManager *locationManager; +@property (nonatomic, strong) NSTimer *locationUpdateTimer; +@property (nonatomic) NSUInteger index; +@property (strong, nonatomic) NSDictionary *routeCoordinates; +@property (strong, nonatomic) NSArray *coordinates; + +@end + +@implementation MBXCustomLocationManager + +@synthesize delegate; + +- (instancetype)init +{ + if (self = [super init]) { + _locationManager = [[CLLocationManager alloc] init]; + _index = 0; + } + return self; +} + +- (CLAuthorizationStatus)authorizationStatus +{ + return [CLLocationManager authorizationStatus]; +} + +- (void)setHeadingOrientation:(CLDeviceOrientation)headingOrientation +{ + _locationManager.headingOrientation = headingOrientation; +} + +- (CLDeviceOrientation)headingOrientation +{ + return _locationManager.headingOrientation; +} + +- (void)requestAlwaysAuthorization +{ + [self.locationManager requestAlwaysAuthorization]; +} + +- (void)requestWhenInUseAuthorization +{ + [self.locationManager requestWhenInUseAuthorization]; +} + +- (void)startUpdatingHeading +{ + [self.locationManager startUpdatingHeading]; +} + +- (void)startUpdatingLocation +{ + [self loadRouteCoordinates]; + self.locationUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0.8 + target:self + selector:@selector(updateLocation) + userInfo:nil + repeats:YES]; +} + +- (void)stopUpdatingHeading +{ + [self.locationManager stopUpdatingHeading]; +} + +- (void)stopUpdatingLocation +{ + [self.locationUpdateTimer invalidate]; + self.locationUpdateTimer = nil; +} + +- (void)dismissHeadingCalibrationDisplay +{ + [self.locationManager dismissHeadingCalibrationDisplay]; +} + +- (void)dealloc +{ + [self.locationManager stopUpdatingLocation]; + [self.locationManager stopUpdatingHeading]; + self.delegate = nil; +} + +#pragma mark - Location Updates + +- (void)loadRouteCoordinates +{ + NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"simple_route" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:filePath]; + _routeCoordinates = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + _coordinates = [self.routeCoordinates objectForKey:@"coordinates"]; +} + +- (void)updateLocation +{ + if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) { + + if (self.index >= [self.coordinates count] ) { + self.index = 0; + self.coordinates = [[self.coordinates reverseObjectEnumerator] allObjects]; + } + NSArray *loc = self.coordinates[self.index]; + CLLocationDegrees latitude = [[loc objectAtIndex:1] doubleValue]; + CLLocationDegrees longitude = [[loc objectAtIndex:0] doubleValue]; + CLLocation *location = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude]; + self.index++; + [self.delegate locationManager:self didUpdateLocations:@[location]]; + } +} + +- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading +{ + if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateHeading:)]) { + [self.delegate locationManager:self didUpdateHeading:newHeading]; + } +} + +- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager +{ + if ([self.delegate respondsToSelector:@selector(locationManagerShouldDisplayHeadingCalibration:)]) { + return [self.delegate locationManagerShouldDisplayHeadingCalibration:self]; + } + + return NO; +} + +- (void)locationManager:(CLLocationManager *)locationManager didFailWithError:(nonnull NSError *)error { + if ([self.delegate respondsToSelector:@selector(locationManager:didFailWithError:)]) { + [self.delegate locationManager:self didFailWithError:error]; + } +} + +@end + +@interface MBXCustomLocationViewController () + +@property (strong, nonatomic) MGLMapView *mapView; + +@end + +@implementation MBXCustomLocationViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds]; + + self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + MBXCustomLocationManager *mbxLocationManager = [[MBXCustomLocationManager alloc] init]; + self.mapView.locationManager = mbxLocationManager; + // Set the map’s center coordinate and zoom level. + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(37.788380, -122.400121) + zoomLevel:13 + animated:NO]; + + [self.view addSubview:self.mapView]; + self.mapView.showsUserLocation = YES; +} + +- (void)viewWillDisappear:(BOOL)animated { + self.mapView.showsUserLocation = NO; +} + +@end diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 167f07d29..71bad66ae 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -98,6 +98,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { MBXSettingsMiscellaneousLocalizeLabels, MBXSettingsMiscellaneousShowSnapshots, MBXSettingsMiscellaneousShouldLimitCameraChanges, + MBXSettingsMiscellaneousShowCustomLocationManager, MBXSettingsMiscellaneousPrintLogFile, MBXSettingsMiscellaneousDeleteLogFile, }; @@ -444,6 +445,7 @@ CLLocationCoordinate2D randomWorldCoordinate() { [NSString stringWithFormat:@"Show Labels in %@", (_localizingLabels ? @"Default Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @"Show Snapshots", [NSString stringWithFormat:@"%@ Camera Changes", (_shouldLimitCameraChanges ? @"Unlimit" : @"Limit")], + @"View Route Simulation", ]]; if (self.debugLoggingEnabled) @@ -682,6 +684,11 @@ CLLocationCoordinate2D randomWorldCoordinate() { [self performSegueWithIdentifier:@"ShowSnapshots" sender:nil]; break; } + case MBXSettingsMiscellaneousShowCustomLocationManager: + { + [self performSegueWithIdentifier:@"ShowCustomLocationManger" sender:nil]; + break; + } case MBXSettingsMiscellaneousShouldLimitCameraChanges: { self.shouldLimitCameraChanges = !self.shouldLimitCameraChanges; diff --git a/platform/ios/app/Main.storyboard b/platform/ios/app/Main.storyboard index 72c5cd013..3e8a0ad02 100644 --- a/platform/ios/app/Main.storyboard +++ b/platform/ios/app/Main.storyboard @@ -107,6 +107,7 @@ + @@ -432,6 +433,21 @@ + + + + + + + + + + + + + + + diff --git a/platform/ios/app/simple_route.json b/platform/ios/app/simple_route.json new file mode 100644 index 000000000..8b910dbda --- /dev/null +++ b/platform/ios/app/simple_route.json @@ -0,0 +1,184 @@ +{ + "coordinates":[ + [ + -122.39899, + 37.787357 + ], + [ + -122.398818, + 37.78722 + ], + [ + -122.398794, + 37.7872 + ], + [ + -122.398259, + 37.786773 + ], + [ + -122.398984, + 37.786206 + ], + [ + -122.399053, + 37.786151 + ], + [ + -122.399379, + 37.785888 + ], + [ + -122.399614, + 37.785697 + ], + [ + -122.399884, + 37.785478 + ], + [ + -122.400382, + 37.78509 + ], + [ + -122.400478, + 37.785015 + ], + [ + -122.400599, + 37.785111 + ], + [ + -122.4012, + 37.785587 + ], + [ + -122.401495, + 37.785825 + ], + [ + -122.401705, + 37.785993 + ], + [ + -122.402041, + 37.786261 + ], + [ + -122.402476, + 37.786603 + ], + [ + -122.402573, + 37.78668 + ], + [ + -122.403019, + 37.787031 + ], + [ + -122.403315, + 37.78728 + ], + [ + -122.403358, + 37.787324 + ], + [ + -122.403382, + 37.787356 + ], + [ + -122.403398, + 37.787392 + ], + [ + -122.403405, + 37.787425 + ], + [ + -122.403415, + 37.787486 + ], + [ + -122.403434, + 37.787654 + ], + [ + -122.403436, + 37.787676 + ], + [ + -122.40344, + 37.787698 + ], + [ + -122.403444, + 37.787729 + ], + [ + -122.403464, + 37.787825 + ], + [ + -122.403476, + 37.787877 + ], + [ + -122.403497, + 37.787965 + ], + [ + -122.403591, + 37.788436 + ], + [ + -122.403684, + 37.788901 + ], + [ + -122.403774, + 37.789349 + ], + [ + -122.403798, + 37.789469 + ], + [ + -122.403872, + 37.789833 + ], + [ + -122.404232, + 37.789788 + ], + [ + -122.405435, + 37.789635 + ], + [ + -122.406, + 37.789562 + ], + [ + -122.406982, + 37.789436 + ], + [ + -122.407475, + 37.789373 + ], + [ + -122.408599, + 37.789231 + ], + [ + -122.408616, + 37.789229 + ], + [ + -122.408451, + 37.788454 + ] + ] +} \ No newline at end of file diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 41b6881db..2a9c9b576 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -30,6 +30,8 @@ 1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1F06668D1EC64F8E001C16D7 /* MGLLight.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F0666891EC64F8E001C16D7 /* MGLLight.mm */; }; + 1F26B6C120E189C9007BCC21 /* MBXCustomLocationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F26B6C020E189C9007BCC21 /* MBXCustomLocationViewController.m */; }; + 1F26B6C320E1A351007BCC21 /* simple_route.json in Resources */ = {isa = PBXBuildFile; fileRef = 1F26B6C220E1A351007BCC21 /* simple_route.json */; }; 1F7454921ECBB42C00021D39 /* MGLLight.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F0666891EC64F8E001C16D7 /* MGLLight.mm */; }; 1F7454931ECBB43F00021D39 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1F7454961ECD450D00021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454941ECD450D00021D39 /* MGLLight_Private.h */; }; @@ -38,6 +40,12 @@ 1F95931D1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */; }; 1FC4817D2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; }; 1FC4817F2098CD80000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; }; + 1FCAE2A220B872A400C577DD /* MGLLocationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FCAE2A020B872A400C577DD /* MGLLocationManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1FCAE2A320B872A400C577DD /* MGLLocationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FCAE2A020B872A400C577DD /* MGLLocationManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1FCAE2A420B872A400C577DD /* MGLLocationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FCAE2A120B872A400C577DD /* MGLLocationManager.m */; }; + 1FCAE2A520B872A400C577DD /* MGLLocationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FCAE2A120B872A400C577DD /* MGLLocationManager.m */; }; + 1FCAE2A820B88B3800C577DD /* MGLLocationManager_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FCAE2A620B88B3800C577DD /* MGLLocationManager_Private.h */; }; + 1FCAE2A920B88B3800C577DD /* MGLLocationManager_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FCAE2A620B88B3800C577DD /* MGLLocationManager_Private.h */; }; 30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; }; 30E578181DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; }; 30E578191DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */; }; @@ -752,10 +760,16 @@ 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; }; 1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; }; 1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = ""; }; + 1F26B6BF20E189C9007BCC21 /* MBXCustomLocationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MBXCustomLocationViewController.h; sourceTree = ""; }; + 1F26B6C020E189C9007BCC21 /* MBXCustomLocationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MBXCustomLocationViewController.m; sourceTree = ""; }; + 1F26B6C220E1A351007BCC21 /* simple_route.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = simple_route.json; sourceTree = ""; }; 1F7454941ECD450D00021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = ""; }; 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLLightTest.mm; path = ../../darwin/test/MGLLightTest.mm; sourceTree = ""; }; 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = ""; }; 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSPredicate+MGLPrivateAdditions.h"; sourceTree = ""; }; + 1FCAE2A020B872A400C577DD /* MGLLocationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLLocationManager.h; sourceTree = ""; }; + 1FCAE2A120B872A400C577DD /* MGLLocationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLLocationManager.m; sourceTree = ""; }; + 1FCAE2A620B88B3800C577DD /* MGLLocationManager_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLLocationManager_Private.h; sourceTree = ""; }; 20DABE861DF78148007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Foundation.strings"; sourceTree = ""; }; 20DABE881DF78148007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 20DABE8A1DF78149007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Root.strings"; sourceTree = ""; }; @@ -1697,6 +1711,7 @@ 353BAEF51D646370009A8DA9 /* amsterdam.geojson */, DA1DC96C1CB6C6CE006E619F /* points.geojson */, DA1DC96D1CB6C6CE006E619F /* polyline.geojson */, + 1F26B6C220E1A351007BCC21 /* simple_route.json */, DA1DC96F1CB6C6CE006E619F /* threestates.geojson */, DD4823721D94AE6C00EB71B7 /* fill_filter_style.json */, DD4823731D94AE6C00EB71B7 /* line_filter_style.json */, @@ -1770,6 +1785,8 @@ DA1DC9691CB6C6B7006E619F /* MBXOfflinePacksTableViewController.m */, 927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */, 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */, + 1F26B6BF20E189C9007BCC21 /* MBXCustomLocationViewController.h */, + 1F26B6C020E189C9007BCC21 /* MBXCustomLocationViewController.m */, DA1DC9531CB6C1C2006E619F /* MBXViewController.h */, DA1DC99A1CB6E064006E619F /* MBXViewController.m */, 632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */, @@ -1911,6 +1928,9 @@ DA8847EE1CBAFA5100AB86E3 /* MGLTypes.h */, DA8848111CBAFA6200AB86E3 /* MGLTypes.m */, 35E1A4D71D74336F007AA97F /* MGLValueEvaluator.h */, + 1FCAE2A020B872A400C577DD /* MGLLocationManager.h */, + 1FCAE2A620B88B3800C577DD /* MGLLocationManager_Private.h */, + 1FCAE2A120B872A400C577DD /* MGLLocationManager.m */, ); name = Foundation; path = ../darwin/src; @@ -2227,6 +2247,7 @@ 35E79F201D41266300957B9E /* MGLStyleLayer_Private.h in Headers */, FA68F14A1E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.h in Headers */, 353933FB1D3FB7C0003F57D7 /* MGLRasterStyleLayer.h in Headers */, + 1FCAE2A820B88B3800C577DD /* MGLLocationManager_Private.h in Headers */, DA8847EF1CBAFA5100AB86E3 /* MGLAccountManager.h in Headers */, DA35A2C91CCAAAD200E826B2 /* NSValue+MGLAdditions.h in Headers */, 3510FFEA1D6D9C7A00F413B2 /* NSComparisonPredicate+MGLAdditions.h in Headers */, @@ -2237,6 +2258,7 @@ 35D3A1E61E9BE7EB002B38EE /* MGLScaleBar.h in Headers */, 0778DD431F67556700A73B34 /* MGLComputedShapeSource.h in Headers */, DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */, + 1FCAE2A220B872A400C577DD /* MGLLocationManager.h in Headers */, DACA86262019218600E9693A /* MGLRasterDEMSource.h in Headers */, 353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */, 92F2C3ED1F0E3C3A00268EC0 /* MGLRendererFrontend.h in Headers */, @@ -2377,6 +2399,7 @@ DA72620C1DEEE3480043BB89 /* MGLOpenGLStyleLayer.h in Headers */, 35CE61831D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */, 96E516F32000597100A02306 /* NSDictionary+MGLAdditions.h in Headers */, + 1FCAE2A920B88B3800C577DD /* MGLLocationManager_Private.h in Headers */, 96E516F02000595800A02306 /* NSBundle+MGLAdditions.h in Headers */, 96E516F920005A3500A02306 /* MGLFaux3DUserLocationAnnotationView.h in Headers */, 96E516F22000596D00A02306 /* NSException+MGLAdditions.h in Headers */, @@ -2416,6 +2439,7 @@ 968F36B51E4D128D003A5522 /* MGLDistanceFormatter.h in Headers */, 4018B1CB1CDC288E00F666AF /* MGLAnnotationView.h in Headers */, DABFB85F1CBE99E500D62B32 /* MGLGeometry.h in Headers */, + 1FCAE2A320B872A400C577DD /* MGLLocationManager.h in Headers */, 96E516E02000550C00A02306 /* MGLFeature_Private.h in Headers */, 353933F61D3FB785003F57D7 /* MGLBackgroundStyleLayer.h in Headers */, DABFB85D1CBE99E500D62B32 /* MGLAccountManager.h in Headers */, @@ -2736,6 +2760,7 @@ DA1DC9731CB6C6CE006E619F /* threestates.geojson in Resources */, DA821D061CCC6D59007508D4 /* LaunchScreen.storyboard in Resources */, 96E027231E57C76E004B8E66 /* Localizable.strings in Resources */, + 1F26B6C320E1A351007BCC21 /* simple_route.json in Resources */, DD4823751D94AE6C00EB71B7 /* fill_filter_style.json in Resources */, DA1DC99F1CB6E088006E619F /* Assets.xcassets in Resources */, ); @@ -2836,6 +2861,7 @@ 927FBCFC1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m in Sources */, DA1DC99B1CB6E064006E619F /* MBXViewController.m in Sources */, 40FDA76B1CCAAA6800442548 /* MBXAnnotationView.m in Sources */, + 1F26B6C120E189C9007BCC21 /* MBXCustomLocationViewController.m in Sources */, 3E6465D62065767A00685536 /* LimeGreenStyleLayer.m in Sources */, 632281DF1E6F855900D75A5D /* MBXEmbeddedMapViewController.m in Sources */, ); @@ -3000,6 +3026,7 @@ 40834BED1FE05E1800C1BD0D /* MMEEventLogger.m in Sources */, 353AFA161D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */, 40834BF41FE05E1800C1BD0D /* MMETrustKitWrapper.m in Sources */, + 1FCAE2A420B872A400C577DD /* MGLLocationManager.m in Sources */, 40834BEF1FE05E1800C1BD0D /* MMEEventsManager.m in Sources */, 35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */, DA8848241CBAFA6200AB86E3 /* MGLOfflineStorage.mm in Sources */, @@ -3126,6 +3153,7 @@ 40834C011FE05E1800C1BD0D /* MMEEventLogger.m in Sources */, 35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */, 40834C081FE05E1800C1BD0D /* MMETrustKitWrapper.m in Sources */, + 1FCAE2A520B872A400C577DD /* MGLLocationManager.m in Sources */, 40834C031FE05E1800C1BD0D /* MMEEventsManager.m in Sources */, DAA4E42A1CBB730400178DFB /* NSProcessInfo+MGLAdditions.m in Sources */, DAA4E4211CBB730400178DFB /* MGLOfflineStorage.mm in Sources */, diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml index d3aae51b7..f995cc06c 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -60,6 +60,10 @@ custom_categories: children: - MGLCalloutView - MGLCalloutViewDelegate + - name: Location Updates + children: + - MGLLocationManager + - MGLLocationManagerDelegate - name: Styling the Map children: - MGLStyle diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index 04a7a0631..b2439416a 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -2,7 +2,6 @@ #import "MGLMapCamera.h" #import -#import #import "MGLFoundation.h" #import "MGLTypes.h" @@ -22,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol MGLOverlay; @protocol MGLCalloutView; @protocol MGLFeature; +@protocol MGLLocationManager; /** The default deceleration rate for a map view. */ extern MGL_EXPORT const CGFloat MGLMapViewDecelerationRateNormal; @@ -296,6 +296,24 @@ MGL_EXPORT IB_DESIGNABLE #pragma mark Displaying the User’s Location +/** + The object that this map view uses to start and stop the delivery of location-related + updates. + + To receive the current user location, implement the `-[MGLMapViewDelegate mapView:didUpdateUserLocation:]` + and `-[MGLMapViewDelegate mapView:didFailToLocateUserWithError:]` methods. + + If setting this property to `nil` and setting `showsUserLocation` to `YES`, or + if no custom manager is provided this property is set to the default + location manager. + + `MGLMapView` uses a default location manager. If you want to substitute your own + location manager, you should do so by setting this property before setting + `showsUserLocation` to `YES`. To restore the default location manager, + set this property to `nil`. + */ +@property (nonatomic, null_resettable) id locationManager; + /** A Boolean value indicating whether the map may display the user location. @@ -312,6 +330,9 @@ MGL_EXPORT IB_DESIGNABLE `NSLocationAlwaysUsageDescription` in its `Info.plist` to satisfy the requirements of the underlying Core Location framework when enabling this property. + + If you implement a custom location manager, set the `locationManager` before + calling `showsUserLocation`. */ @property (nonatomic, assign) BOOL showsUserLocation; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index e4906e368..cdacfb462 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -73,6 +73,7 @@ #import "MGLAnnotationContainerView_Private.h" #import "MGLAttributionInfo_Private.h" #import "MGLMapAccessibilityElement.h" +#import "MGLLocationManager_Private.h" #include #include @@ -184,7 +185,7 @@ public: @interface MGLMapView () )delegate @@ -4687,15 +4690,29 @@ public: #pragma mark - User Location - +- (void)setLocationManager:(id)locationManager +{ + if (!locationManager) { + [self.locationManager stopUpdatingLocation]; + [self.locationManager stopUpdatingHeading]; + } + self.locationManager.delegate = nil; + _locationManager = locationManager; + _locationManager.delegate = self; +} + - (void)validateLocationServices { BOOL shouldEnableLocationServices = self.showsUserLocation && !self.dormant; - if (shouldEnableLocationServices && ! self.locationManager) + if (shouldEnableLocationServices) { - self.locationManager = [[CLLocationManager alloc] init]; + // If no custom location manager is provided will use the internal implementation. + if (!self.locationManager) { + self.locationManager = [[MGLCLLocationManager alloc] init]; + } - if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) + if (self.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { BOOL requiresWhenInUseUsageDescription = [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){11,0,0}]; BOOL hasWhenInUseUsageDescription = !![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]; @@ -4735,8 +4752,6 @@ public: { [self.locationManager stopUpdatingLocation]; [self.locationManager stopUpdatingHeading]; - self.locationManager.delegate = nil; - self.locationManager = nil; } } @@ -4966,12 +4981,12 @@ public: } } -- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations +- (void)locationManager:(id)manager didUpdateLocations:(NSArray *)locations { [self locationManager:manager didUpdateLocations:locations animated:YES]; } -- (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations:(NSArray *)locations animated:(BOOL)animated +- (void)locationManager:(__unused id)manager didUpdateLocations:(NSArray *)locations animated:(BOOL)animated { CLLocation *oldLocation = self.userLocation.location; CLLocation *newLocation = locations.lastObject; @@ -5198,16 +5213,21 @@ public: return direction; } -- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager +- (BOOL)locationManagerShouldDisplayHeadingCalibration:(id)manager { - if (self.displayHeadingCalibration) [manager performSelector:@selector(dismissHeadingCalibrationDisplay) - withObject:nil + if (self.displayHeadingCalibration) [self performSelector:@selector(dismissHeadingCalibrationDisplay:) + withObject:manager afterDelay:10.0]; return self.displayHeadingCalibration; } -- (void)locationManager:(__unused CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading +- (void)dismissHeadingCalibrationDisplay:(id)manager +{ + [manager dismissHeadingCalibrationDisplay]; +} + +- (void)locationManager:(__unused id)manager didUpdateHeading:(CLHeading *)newHeading { if ( ! _showsUserLocation || self.pan.state == UIGestureRecognizerStateBegan || newHeading.headingAccuracy < 0) return; @@ -5234,7 +5254,7 @@ public: } } -- (void)locationManager:(__unused CLLocationManager *)manager didFailWithError:(NSError *)error +- (void)locationManager:(__unused id)manager didFailWithError:(NSError *)error { if ([error code] == kCLErrorDenied) { diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index 7beb8b766..a0afe2d9c 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -67,3 +67,4 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLMapSnapshotter.h" #import "NSExpression+MGLAdditions.h" #import "NSPredicate+MGLAdditions.h" +#import "MGLLocationManager.h"