web-dev-qa-db-fra.com

Démarrez Location Manager dans iOS 7 à partir de la tâche en arrière-plan

Il semble que dans iOS 7, une application ne puisse plus démarrer Location Manager (en appelant startUpdatingLocation) à partir de la tâche d'arrière-plan.

Dans iOS 6, j'ai utilisé l'approche décrite ici: https://stackoverflow.com/a/646528 pour exécuter la mise à jour de l'emplacement en arrière-plan toutes les n minutes. L'idée était d'exécuter la tâche en arrière-plan avec un minuteur et de démarrer Location Manager lorsque le minuteur le déclenche. Après cela, désactivez Location Manager et démarrez une autre tâche en arrière-plan.

Après la mise à jour vers iOS 7, cette approche ne fonctionne plus. Après avoir démarré Location Manager, une application ne reçoit aucun emplacementManager: didUpdateLocations. Des idées?

27
sash

J'ai trouvé le problème/la solution. Lorsqu'il est temps de démarrer le service de localisation et d'arrêter la tâche en arrière-plan, la tâche en arrière-plan doit être arrêtée avec un délai (j'ai utilisé 1 seconde). Sinon, le service de localisation ne démarre pas. De plus, le service de localisation doit être laissé sur ON pendant quelques secondes (dans mon exemple, c'est 3 secondes).

Autre remarque importante, la durée maximale d'arrière-plan dans iOS 7 est désormais de 3 minutes au lieu de 10 minutes.

Mis à jour le 29 octobre '16

Il y a un cocoapod APScheduledLocationManager qui permet d'obtenir des mises à jour d'emplacement en arrière-plan toutes les n secondes avec la précision d'emplacement souhaitée.

let manager = APScheduledLocationManager(delegate: self)
manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)

Le référentiel contient également un exemple d'application écrit en Swift 3.

Mis à jour le 27 mai 14

Exemple Objective-C:

1) Dans le fichier ".plist", définissez UIBackgroundModes sur "emplacement".

2) Créez une instance de ScheduledLocationManager où vous voulez.

@property (strong, nonatomic) ScheduledLocationManager *slm;

3) Configurez-le

self.slm = [[ScheduledLocationManager alloc]init];
self.slm.delegate = self;
[self.slm getUserLocationWithInterval:60]; // replace this value with what you want, but it can not be higher than kMaxBGTime

4) Mettre en œuvre des méthodes déléguées

-(void)scheduledLocationManageDidFailWithError:(NSError *)error
{
    NSLog(@"Error %@",error);
}

-(void)scheduledLocationManageDidUpdateLocations:(NSArray *)locations
{
    // You will receive location updates every 60 seconds (value what you set with getUserLocationWithInterval)
    // and you will continue to receive location updates for 3 seconds (value of kTimeToGetLocations).
    // You can gather and pick most accurate location
    NSLog(@"Locations %@",locations);
}

Voici l'implémentation de ScheduledLocationManager:

ScheduledLocationManager.h

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>

@protocol ScheduledLocationManagerDelegate <NSObject>

-(void)scheduledLocationManageDidFailWithError:(NSError*)error;
-(void)scheduledLocationManageDidUpdateLocations:(NSArray*)locations;

@end

@interface ScheduledLocationManager : NSObject <CLLocationManagerDelegate>

-(void)getUserLocationWithInterval:(int)interval;

@end

ScheduledLocationManager.m

#import "ScheduledLocationManager.h"

int const kMaxBGTime = 170; // 3 min - 10 seconds (as bg task is killed faster)
int const kTimeToGetLocations = 3; // time to wait for locations

@implementation ScheduledLocationManager
{
    UIBackgroundTaskIdentifier bgTask;
    CLLocationManager *locationManager;
    NSTimer *checkLocationTimer;
    int checkLocationInterval;
    NSTimer *waitForLocationUpdatesTimer;
}

- (id)init
{
    self = [super init];
    if (self) {
        locationManager = [[CLLocationManager alloc] init];
        locationManager.delegate = self;
        locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        locationManager.distanceFilter = kCLDistanceFilterNone;

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
    }
    return self;
}

-(void)getUserLocationWithInterval:(int)interval
{
    checkLocationInterval = (interval > kMaxBGTime)? kMaxBGTime : interval;
    [locationManager startUpdatingLocation];
}

- (void)timerEvent:(NSTimer*)theTimer
{
    [self stopCheckLocationTimer];
    [locationManager startUpdatingLocation];

    // in iOS 7 we need to stop background task with delay, otherwise location service won't start
    [self performSelector:@selector(stopBackgroundTask) withObject:nil afterDelay:1];
}

-(void)startCheckLocationTimer
{
    [self stopCheckLocationTimer];
    checkLocationTimer = [NSTimer scheduledTimerWithTimeInterval:checkLocationInterval target:self selector:@selector(timerEvent:) userInfo:NULL repeats:NO];
}

-(void)stopCheckLocationTimer
{
    if(checkLocationTimer){
        [checkLocationTimer invalidate];
        checkLocationTimer=nil;
    }
}

-(void)startBackgroundTask
{
    [self stopBackgroundTask];
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        //in case bg task is killed faster than expected, try to start Location Service
        [self timerEvent:checkLocationTimer];
    }];
}

-(void)stopBackgroundTask
{
    if(bgTask!=UIBackgroundTaskInvalid){
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }
}

-(void)stopWaitForLocationUpdatesTimer
{
    if(waitForLocationUpdatesTimer){
        [waitForLocationUpdatesTimer invalidate];
        waitForLocationUpdatesTimer =nil;
    }
}

-(void)startWaitForLocationUpdatesTimer
{
    [self stopWaitForLocationUpdatesTimer];
    waitForLocationUpdatesTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeToGetLocations target:self selector:@selector(waitForLoactions:) userInfo:NULL repeats:NO];
}

- (void)waitForLoactions:(NSTimer*)theTimer
{
    [self stopWaitForLocationUpdatesTimer];

    if(([[UIApplication sharedApplication ]applicationState]==UIApplicationStateBackground ||
        [[UIApplication sharedApplication ]applicationState]==UIApplicationStateInactive) &&
       bgTask==UIBackgroundTaskInvalid){
        [self startBackgroundTask];
    }

    [self startCheckLocationTimer];
    [locationManager stopUpdatingLocation];
}

#pragma mark - CLLocationManagerDelegate methods

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    if(checkLocationTimer){
        //sometimes it happens that location manager does not stop even after stopUpdationLocations
        return;
    }

    if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidUpdateLocations:)]) {
        [self.delegate scheduledLocationManageDidUpdateLocations:locations];
    }

    if(waitForLocationUpdatesTimer==nil){
        [self startWaitForLocationUpdatesTimer];
    }
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidFailWithError:)]) {
        [self.delegate scheduledLocationManageDidFailWithError:error];
    }
}

#pragma mark - UIAplicatin notifications

- (void)applicationDidEnterBackground:(NSNotification *) notification
{
    if([self isLocationServiceAvailable]==YES){
        [self startBackgroundTask];
    }
}

- (void)applicationDidBecomeActive:(NSNotification *) notification
{
    [self stopBackgroundTask];
    if([self isLocationServiceAvailable]==NO){
        NSError *error = [NSError errorWithDomain:@"your.domain" code:1 userInfo:[NSDictionary dictionaryWithObject:@"Authorization status denied" forKey:NSLocalizedDescriptionKey]];

        if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidFailWithError:)]) {
            [self.delegate scheduledLocationManageDidFailWithError:error];
        }
    }
}

#pragma mark - Helpers

-(BOOL)isLocationServiceAvailable
{
    if([CLLocationManager locationServicesEnabled]==NO ||
       [CLLocationManager authorizationStatus]==kCLAuthorizationStatusDenied ||
       [CLLocationManager authorizationStatus]==kCLAuthorizationStatusRestricted){
        return NO;
    }else{
        return YES;
    }
}

@end
43
sash

J'ai essayé ta méthode mais ça n'a pas marché de mon côté. Pouvez-vous me montrer votre code?

J'ai trouvé une solution pour résoudre le problème du service de localisation dans iOS 7.

Dans iOS 7, vous ne pouvez pas démarrer le service de localisation en arrière-plan. Si vous souhaitez que le service de localisation continue de fonctionner en arrière-plan, vous devez le démarrer en premier plan et il continuera de fonctionner en arrière-plan.

Si vous étiez comme moi, arrêtez le service de localisation et utilisez la minuterie pour le redémarrer en arrière-plan, cela ne fonctionnera PAS dans iOS 7.

Pour des informations plus détaillées, vous pouvez regarder les 8 premières minutes de la vidéo 307 de la WWDC 2013: https://developer.Apple.com/wwdc/videos/

Mise à jour: Le service de localisation peut fonctionner en arrière-plan également. Veuillez vérifier les services de localisation en arrière-plan ne fonctionnent pas dans iOS 7 pour le message mis à jour avec une solution complète publiée sur Github et un article de blog expliquant les détails.

7
Ricky

Les étapes pour obtenir cette implémentation sont les suivantes:

  1. Ajoutez "Registres d'application pour les mises à jour de localisation" à l'élément 0 dans "Modes d'arrière-plan requis" dans info.plist de votre projet.

  2. Écrivez le code ci-dessous à la fin du lancement de l'application.

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startFetchingLocationsContinously) name:START_FETCH_LOCATION object:nil];
    
  3. Écrivez ci-dessous le code à partir duquel vous souhaitez commencer le suivi

    [[NSNotificationCenter defaultCenter] postNotificationName:START_FETCH_LOCATION object:nil];
    
    AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
                [appDelegate startUpdatingDataBase];
    
  4. Collez le code suivant dans AppDelegate.m

    #pragma mark - Location Update
    -(void)startFetchingLocationsContinously{
        NSLog(@"start Fetching Locations");
        self.locationUtil = [[LocationUtil alloc] init];
        [self.locationUtil setDelegate:self];
        [self.locationUtil startLocationManager];
    }
    
    -(void)locationRecievedSuccesfullyWithNewLocation:(CLLocation*)newLocation oldLocation:(CLLocation*)oldLocation{
        NSLog(@"location received successfullly in app delegate for Laitude: %f and Longitude:%f, and Altitude:%f, and Vertical Accuracy: %f",newLocation.coordinate.latitude,newLocation.coordinate.longitude,newLocation.altitude,newLocation.verticalAccuracy);
    }
    
    -(void)startUpdatingDataBase{
        UIApplication*    app = [UIApplication sharedApplication];
    
        bgTask = UIBackgroundTaskInvalid;
    
        bgTask = [app beginBackgroundTaskWithExpirationHandler:^(void){
            [app endBackgroundTask:bgTask];
        }];
    
        SAVE_LOCATION_TIMER =  [NSTimer scheduledTimerWithTimeInterval:300
                                                                target:self selector:@selector(startFetchingLocationsContinously) userInfo:nil repeats:YES];
    }
    
  5. Ajoutez une classe par son nom "LocationUtil" et collez le code suivant dans le fichier d'en-tête:

    #import <Foundation/Foundation.h>
    #import <CoreLocation/CoreLocation.h>
    @protocol LocationRecievedSuccessfully <NSObject>
    @optional
    -(void)locationRecievedSuccesfullyWithNewLocation:(CLLocation*)newLocation oldLocation:(CLLocation*)oldLocation;
    -(void)addressParsedSuccessfully:(id)address;
    
    @end
    @interface LocationUtil : NSObject <CLLocationManagerDelegate> {
    }
    
    //Properties
    @property (nonatomic,strong) id<LocationRecievedSuccessfully> delegate;
    -(void)startLocationManager;
    

    Et collez le code suivant dans LocationUtil.m

    -(void)startLocationManager{
    
         locationManager = [[CLLocationManager alloc] init];
         locationManager.delegate = self;
         [locationManager setPausesLocationUpdatesAutomatically:YES]; //Utkarsh 20sep2013
         //[locationManager setActivityType:CLActivityTypeFitness];
         locationManager.distanceFilter = kCLDistanceFilterNone;
         locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
         [locationManager startUpdatingLocation];
    
         //Reverse Geocoding.
         geoCoder=[[CLGeocoder alloc] init];
    
        //set default values for reverse geo coding.
    }
    
    //for iOS<6
    - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
      //call delegate Method
      [delegate locationRecievedSuccesfullyWithNewLocation:newLocation oldLocation:oldLocation];
    
      NSLog(@"did Update Location");
    }
    
    //for iOS>=6.
    
    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
    
      CLLocation *newLocation = [locations objectAtIndex:0];
      CLLocation *oldLocation = [locations objectAtIndex:0];
    
      [delegate locationRecievedSuccesfullyWithNewLocation:newLocation oldLocation:oldLocation];
      NSLog(@"did Update Locationsssssss");
    }
    
6
Utkarsh Goel