如何最简单地获取iPhone的当前位置?

22

我已经知道如何使用CLLocationManager,因此我可以按照传统的方式使用委托等方法。但是我想要一个便捷的方法,只需获取当前位置一次并阻塞直到获得结果。


你应该澄清问题:如果你知道如何使用CLLocationManager-实现这样一个“convenience method”的问题是什么? - tcurdt
10
我认为问题在于它并不容易。通常,它会先返回一个旧的位置,接着是一个非常不准确的位置,然后是逐渐变得更好的位置,你在哪个时刻决定你想要的那一个呢?这真的取决于你自己。 - rustyshelf
5个回答

44

我所做的是实现一个单例类来管理来自核心位置的更新。 要访问我的当前位置,我执行以下操作:CLLocation *myLocation = [[LocationManager sharedInstance] currentLocation]; 如果您想要阻塞主线程,可以像这样做:

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
}

然而,正如已经指出的那样,阻塞主线程可能不是一个好主意,但这可以作为构建应用程序的一个很好的起点。您还会注意到,我编写的类检查位置更新的时间戳,并忽略任何旧的位置,以防止从核心位置获取过时数据的问题。

这是我编写的单例类。请注意,它还有一些不完善的地方:

#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

感谢提供这个解决方案。这是一个非常好的小控制器。但我发现其中一个问题,就是locationKnown方法不够准确,因为它总是返回YES,这是由于currentLocation在init方法中被初始化。下面是另一个版本的方法,它使用了speed属性来判断用户位置是否已经接收到:-(BOOL) locationKnown { if (round(currentLocation.speed) == -1) return NO; else return YES; } - Benjamin Pearson
2
你只是分配了资源,但还没有释放它。请问您能告诉我在哪里释放它,以免引起任何问题吗? - IDev

7

没有这样的便利,您也不应该自己创建。在像iPhone这样的设备上,“等待结果返回”是极其糟糕的编程实践。检索位置可能需要几秒钟时间,您绝不能让用户等待,而委托确保他们不必如此。


线程,在完成回调时进行组合是可行的。其中回调在发起的运行循环上进行调度。这将为您提供与CLLocationManager的委托已经具有的接口完全相同的界面。在最终用户应用程序中,“阻塞直到完成”从未是一个好的设计。 - Chris Hanson
18
我不同意,“blocks until done” 在各种应用中有许多合法的用途,并且并不等同于“在完成之前冻结UI”。试图使所有事情都异步化只会导致奇怪的行为和有缺陷的应用程序。如果需要显示的信息尚不可用,谁关心UI出现/响应的速度有多快?异步操作有其位置,阻塞操作也是如此。这不是一种情况,其中一个好而另一个不好。 - aroth
1
谁在乎UI的出现/响应速度?用户在乎。冻结的UI令人沮丧,应尽一切可能避免。 - Vincent Tourraine
异步代码更难调试,因为它失去了清晰度。轮询循环如果使用不当会导致100%的CPU负载并耗尽电池。sleep()会导致UI延迟。通知和/或代理是正确的方法(虽然回调很丑陋...块是胜利者)。工程学涉及权衡、懒惰和满足用户的需求。 - user246672

4

除非你自己编写,否则没有“方便方法”,但你仍然需要在任何自定义代码中实现委托方法以使事情变得“方便”。

委托模式存在的原因,在于委托是Objective-C的重要组成部分,我建议你熟悉它们。


0

我很感激Brad Smith的回答。在实施时,我发现他使用的其中一种方法已经在iOS6中被弃用。为了编写适用于iOS5和iOS6的代码,请使用以下方法:

- (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

我简化并合并多个答案,使得仅当位置有效时才更新位置。

它还可在OSX和iOS下使用。

这假设用户突然需要当前位置。如果在此示例中需要100毫秒以上,则认为是错误。(假设GPS IC & | Wifi (Apple's Skyhook clone)已经启动并已有良好的修复。)

#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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接