2009-01-19 14 views
22

So già come usare CLLocationManager, quindi potrei farlo nel modo più difficile, con i delegati e tutto il resto.Qual è il modo più semplice per ottenere la posizione corrente di un iPhone?

Ma mi piacerebbe avere un metodo di comodità che ottiene solo la posizione corrente, una volta, e blocca fino a quando non ottiene il risultato.

+0

Si dovrebbe chiarire la questione: se si sa come utilizzare CLLocationManager - qual è il problema nell'implementazione di un tale "metodo di convenienza"? – tcurdt

+10

Penso che il problema sia che non è così facile. Normalmente restituisce prima una vecchia posizione, seguita da una molto selvaggia, seguita da una progressivamente migliore, a che punto si decide di avere quella che si desidera? Dipende solo da te. – rustyshelf

+0

Buon punto, arrugginito! –

risposta

4

Non esistono "metodi di convenienza" a meno che non li si codifichi da soli, ma sarà comunque necessario implementare i metodi delegati in qualsiasi codice personalizzato utilizzato per rendere le cose "convenienti".

Il modello di delegato è lì per un motivo, e dato che i delegati sono una parte importante di Objective-C, ti consiglio di metterti a loro agio.

45

Quello che faccio è implementare una classe singleton per gestire gli aggiornamenti dalla posizione principale. Per accedere alla mia posizione attuale, faccio un CLLocation *myLocation = [[LocationManager sharedInstance] currentLocation]; Se si voleva bloccare il thread principale si potrebbe fare qualcosa di simile:

while ([[LocationManager sharedInstance] locationKnown] == NO){ 
    //blocking here 
    //do stuff here, dont forget to have some kind of timeout to get out of this blocked //state 
} 

Tuttavia, come è stato già sottolineato, bloccare il thread principale non è probabilmente un buona idea, ma questo può essere un buon punto di partenza mentre stai costruendo qualcosa. Noterai inoltre che la classe che ho scritto controlla il timestamp sugli aggiornamenti di posizione e ignora quelli vecchi, per prevenire il problema di ottenere dati obsoleti dalla posizione principale.

Questa è la classe singleton che ho scritto. Si noti che è un po 'approssimativo:

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

@interface LocationController : NSObject <CLLocationManagerDelegate> { 
    CLLocationManager *locationManager; 
    CLLocation *currentLocation; 
} 

+ (LocationController *)sharedInstance; 

-(void) start; 
-(void) stop; 
-(BOOL) locationKnown; 

@property (nonatomic, retain) CLLocation *currentLocation; 

@end 
@implementation LocationController 

@synthesize currentLocation; 

static LocationController *sharedInstance; 

+ (LocationController *)sharedInstance { 
    @synchronized(self) { 
     if (!sharedInstance) 
      sharedInstance=[[LocationController alloc] init];  
    } 
    return sharedInstance; 
} 

+(id)alloc { 
    @synchronized(self) { 
     NSAssert(sharedInstance == nil, @"Attempted to allocate a second instance of a singleton LocationController."); 
     sharedInstance = [super alloc]; 
    } 
    return sharedInstance; 
} 

-(id) init { 
    if (self = [super init]) { 
     self.currentLocation = [[CLLocation alloc] init]; 
     locationManager = [[CLLocationManager alloc] init]; 
     locationManager.delegate = self; 
     [self start]; 
    } 
    return self; 
} 

-(void) start { 
    [locationManager startUpdatingLocation]; 
} 

-(void) stop { 
    [locationManager stopUpdatingLocation]; 
} 

-(BOOL) locationKnown { 
    if (round(currentLocation.speed) == -1) return NO; else return YES; 
} 

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { 
    //if the time interval returned from core location is more than two minutes we ignore it because it might be from an old session 
    if (abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 120) {  
     self.currentLocation = newLocation; 
    } 
} 

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { 
    UIAlertView *alert; 
    alert = [[UIAlertView alloc] initWithTitle:@"Error" message:[error description] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 
    [alert show]; 
    [alert release]; 
} 

-(void) dealloc { 
    [locationManager release]; 
    [currentLocation release]; 
    [super dealloc]; 
} 

@end 
+0

Grazie per questa soluzione. È un piccolo controller carino da avere a disposizione.Una cosa che ho trovato è che il metodo locationKnown non era molto accurato: restituiva sempre SÌ perché currentLocation è inizializzato nel metodo init. Ecco una versione alternativa del metodo che utilizza la proprietà speed per determinare se la posizione dell'utente è stata ricevuta: - (BOOL) locationKnown { if (round (currentLocation.speed) == -1) return NO; altro ritorno SÌ; } –

+3

Ho modificato il codice, spero di averlo capito correttamente. –

+2

Hai appena assegnato le risorse ma non l'hai rilasciato. Puoi dirmi dove lo rilascerai che non causerà alcun problema. –

7

Non esiste tale comodità e non è necessario crearne di propri. "Blocchi fino a quando non si ottiene il risultato" è estremamente male la pratica di programmazione su un dispositivo come l'iPhone. Possono essere necessari alcuni secondi per recuperare una posizione; non dovresti mai fare aspettare i tuoi utenti in quel modo, e i delegati si assicurano che non lo facciano.

+1

Non è per quello che sono i fili? –

+0

Thread, sì, se combinato con una richiamata al termine. Dove è programmato il callback sul ciclo di esecuzione di origine. Che ti offre un'interfaccia esattamente come ha già il delegato di CLLocationManager. "Blocca fino al termine" non è mai un buon design nelle app degli utenti finali. –

+18

Non sono d'accordo, "blocchi fino al completamento" ha molti usi legittimi in tutti i tipi di app e non è sinonimo di "blocca l'interfaccia utente fino al completamento". Cercare di fare * tutto * in modo asincrono porta a comportamenti bizzarri e app buggy. A chi importa quanto velocemente appare l'interfaccia utente/risponde se le informazioni che deve visualizzare non sono ancora disponibili? Le operazioni asincrone hanno il loro posto, e così anche quelle bloccanti. Non è un caso dove uno è buono e l'altro è cattivo. – aroth

0

Ho apprezzato la risposta di Brad Smith. Implementandolo ho scoperto che uno dei metodi che usa è deprecato da iOS6. Per scrivere il codice che funziona sia con iOS5 e iOS6, utilizzare il seguente:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { 
    if (abs([[locations lastObject] timeIntervalSinceDate:[NSDate date]]) < 120) { 
     [self setCurrentLocation:[locations lastObject]]; 
    } 
} 

// Backward compatibility with iOS5. 
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { 
    NSArray *locations = [NSArray arrayWithObjects:oldLocation, newLocation, nil]; 
    [self locationManager:manager didUpdateLocations:locations]; 
} 
0

ho semplificato e combinate più risposte a cui la posizione viene aggiornata solo se è valido.

Funziona anche con OSX e iOS.

Questo presuppone il caso d'uso in cui la posizione corrente viene improvvisamente desiderata dall'utente. Se in questo esempio occorrono più di 100 ms, viene considerato un errore. (Assume l'IC GPS & | Wifi (Skyhook clone di Apple) è già licenziato e ha una buona correzione già.)

#import "LocationManager.h" 

// wait up to 100 ms 
CLLocation *location = [LocationManager currentLocationByWaitingUpToMilliseconds:100]; 
if (!location) { 
    NSLog(@"no location :("); 
    return; 
} 
// location is good, yay 

https://gist.github.com/6972228

+0

Funziona benissimo! –

Problemi correlati