问题
I'm developing an iOS app based on the map and location tracking. When the user first boots the app it asks for permission to track location etc. just fine. The only problem is while it's doing that, I have code setting up the initial map view and other location-based variables before the user has clicked OK.
I've found I can put these initiation steps after a while loop that waits for the user to change the location manager's permissions as below but this can't possibly be best practice, not to mention it leads to some strange behavior in the transition between the splash screen and the map:
BOOL firstrun = TRUE;
while ([[locationManager class] authorizationStatus] == kCLAuthorizationStatusDenied || [[locationManager class] authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
NSLog(@"Waiting for location permission");
}
...initiation code...
Is there a "location access granted" listener for the alert box or a similar function in the location manager delegate I don't know about? I see no such method in the docs. Anyone know what the best practice is here? Thank you so much.
EDIT I start my location tracking as follows:
if (nil == locationManager)
locationManager = [[CLLocationManager alloc] init];
[locationManager startMonitoringSignificantLocationChanges];
self.musicmap.delegate = self;
[self.mymap setShowsUserLocation:true];
Thanks
回答1:
I would recommend making your class a CLLocationManagerDelegate and then implementing this method:
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
//Your code goes here
}
More information about CLLocationManagerDelegate can be found here.
Hope that helps!
回答2:
I had a similar problem with my application and the app doing things before the user has time to accept or decline the location permission dialogue. Here is what I ended up doing.
-(BOOL)locationAuthorizationStatus {
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
// user has not authorized us to use location
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Location Denied", @"Location Denied")
message:NSLocalizedString(@"This app does not have permission to access your location. Please enable location access in device settings.", @"Message stating this app does not have permission to access your location and to enable location permission in settings")
delegate:self
cancelButtonTitle:NSLocalizedString(@"Ok", @"Ok")
otherButtonTitles: nil];
[alert show];
return NO;
}
// Check if region monitoring is available for this device
if (![CLLocationManager regionMonitoringAvailable]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Geofencing Unavailable", @"Geofencing Unavailable")
message:NSLocalizedString(@"This device is not able to monitor regions", @"Message stating this device is not able to monitor regions")
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:NSLocalizedString(@"Ok", @"Ok"), nil];
[alert show];
return NO;
} else {
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
// trigger a location check to prompt user for authorization
LocationManagerController *locationController = [LocationManagerController sharedManager];
[locationController.locationManager startUpdatingLocation];
// the dialogue box is triggered here
[locationController.locationManager stopUpdatingLocation];
_waitingOnAuthorization = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkForAuthorizationStatusChange) name:@"WaitingOnAuthorizationStatus" object:nil];
return NO;
}
}
return YES;
}
-(void)checkForAuthorizationStatusChange {
if (_waitingOnAuthorization) {
// this should only catch location change on first time
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
// user approved location services
} else {
// user declined authorization
}
// set flag back to NO
_waitingOnAuthorization = NO;
}
// remove our notification observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
You will have to add the variables that apply to your use case. But here is the gist of it.
- Check for authorization status via BOOL method (locationAuthorizationStatus)
- If ok to use location, returns YES do whatever you want
- If not, returns NO and alerts user
- If first time, will fire up the location manager to trigger location dialogue, then stop it to save battery, sets the flag and sets a notification so you will know when the user has hit yes or no.
- The notification fires the method checkAuthorizationChangeStatus and rechecks permissions to see what the user did. From there, you can call any methods you need based on the users choice.
Apple does not have any delegate methods to catch this selection, so the only way around it is kinda of hacky. This method has worked well for me. Slightly hacky, but works. Hope this helps.
回答3:
//Start up motion manager, not sure if you need this for location manager
motionManager = [[CMMotionManager alloc] init];
if (motionManager.accelerometerAvailable) {
motionManager.accelerometerUpdateInterval = 1.0/2.0;
[motionManager startAccelerometerUpdates];
}
locationManager = [[CLLocationManager alloc] init];
//We will be the location manager delegate
locationManager.delegate = self;
//Track position at the 100m accuracy
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
//We want to see all location updates, regardless of distance change
locationManager.distanceFilter = 0.0;
[locationManager startUpdatingLocation];
Put the above somewhere in your "viewDidLoad" or appDelegate "didFinishLaunchingWithOption"
The following handles the updates and stuff. bestLocation is a CLLocation. You'll need to implement CLLocationManagerDelegate. TWO_MINUTES = 120
- (BOOL)isBetterLocation:(CLLocation *)location {
if (bestLocation == nil){
//best location not set yet, so it's a better location by default
return YES;
}
// Figure out how long it's been since we got a better location
NSTimeInterval timeDelta = [location.timestamp timeIntervalSinceDate:bestLocation.timestamp];
BOOL isSignificantlyNewer = timeDelta > TWO_MINUTES;
BOOL isSignificantlyOlder = timeDelta < -TWO_MINUTES;
BOOL isNewer = timeDelta > 0;
if (isSignificantlyNewer) {
return YES;
}else if (isSignificantlyOlder) {
return NO;
}
CLLocationAccuracy accuracyDelta = location.horizontalAccuracy - bestLocation.horizontalAccuracy;
//You want accuracy to be low
BOOL isLessAccurate = accuracyDelta > 0;
BOOL isMoreAccurate = accuracyDelta < 0;
BOOL isDifferent = location.coordinate.latitude != bestLocation.coordinate.latitude ||
location.coordinate.longitude != bestLocation.coordinate.longitude;
if (isMoreAccurate) {
return YES;
} else if (isNewer && !isLessAccurate && isDifferent) {
return YES;
}
return NO;
}
#pragma mark - Location manager delegate
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
if ([self isBetterLocation:newLocation]){
self.bestLocation = newLocation;
} else {
[locationManager stopUpdatingLocation];
isLocating = NO;
NSLog(@"AppDelegate: Turning off location manager >>>>>>>>>>>>>>>>>>>>>");
}
}
Note: You don't need the "isBetterLocation" method. That will just check the location and turn off the location manager after like 2 minutes so you don't wear down the user's battery. Sometimes you don't want the manager off though, so you can leave it out if you want.
I don't know how much of this you've done already. This is the way I did it.
来源:https://stackoverflow.com/questions/13981679/ios-waiting-for-location-manager-to-init