iOS 8中位置服务无法工作

622

我的应用在iOS 7上可以正常运行,但在iOS 8 SDK上不起作用。

CLLocationManager没有返回位置信息,而且我在设置 -> 定位服务下也看不到我的应用程序。我在Google上搜索了一下这个问题,但是没有找到任何东西。可能出了什么问题?


19
我曾在这里发布关于iOS 8中位置管理器更改的一些内容:http://nevan.net/2014/09/core-location-manager-changes-in-ios-8/ - nevan king
2
你可以尝试使用这个库,它简化了核心位置API并提供了一个漂亮的基于块的接口,并规范了不同iOS版本之间的所有差异(完全透明披露:我是作者):https://github.com/lmirosevic/GBLocation - lmirosevic
你可以参考这篇文章:https://dev59.com/nYPba4cB1Zd3GeqPtILE#26402196。 - Ramesh Muthe
2
@nevanking 先生!您需要一顶皇冠!自从更改以来,我一直在与这个问题作斗争,但还没有找到一个“指南”来解决它,而且这个指南还要适合新手。您的指南让像我这样的白痴自己处理了这个问题。非常感谢您提供的链接。 - Patrick R
你可以参考这个链接: iOS:Core Location不询问用户权限 - Alok
显示剩余2条评论
26个回答

1097

我最终解决了自己的问题。

显然在iOS 8 SDK中,需要调用CLLocationManagerrequestAlwaysAuthorization(后台定位)或requestWhenInUseAuthorization(仅前台定位)才能开始定位更新。

此外,还需要在Info.plist中添加NSLocationAlwaysUsageDescriptionNSLocationWhenInUseUsageDescription键以显示在提示中的消息。 添加这些内容解决了我的问题。

输入图像描述

获取更多详细信息,请参见: iOS 8中核心位置管理器的更改


6
您能分享一下您在 Info.plist 中更新的确切值吗?我在我的 Info.plist 中找不到那个键。我已经添加了它,但似乎仍然不能正常工作。我正在使用 Swift、iOS8 和 XCode 6 beta 4。 - A B Vijay Kumar
4
@Vjay - 你需要在编辑器中修改Info.plist文件,无法在Xcode中设置这些键(截至beta 6)。 - Kendall Helmstetter Gelner
11
提醒一下,如果你不想向用户显示自定义的解释,只需将该键设置为空字符串。 - nonamelive
7
为了使其工作,您需要保留您的locationManager。因此,请将您的CLLocationManager设置为强引用属性。 - Vassily
273
我喜欢这一点,如果你省略了plist条目,你不会收到任何错误、警告、日志信息或其他提示。基本上,苹果创建了一个API调用,如果没有适当的plist条目,它就什么都不做。感谢 @OrtwinGentz 的发现。 - MobileVet
显示剩余18条评论

319

我曾经遇到同样的问题,Xcode会给你一个错误提示:

尝试在未提示用户授权的情况下启动MapKit位置更新。必须先调用-[CLLocationManagerrequestWhenInUseAuthorization]-[CLLocationManager requestAlwaysAuthorization]

但是即使您实现了上述方法之一,它也不会提示用户,除非在info.plist中有NSLocationAlwaysUsageDescriptionNSLocationWhenInUseUsageDescription的条目。

请将以下行添加到您的info.plist中,其中字符串值代表您需要访问用户位置的原因

<key>NSLocationWhenInUseUsageDescription</key>
<string>This application requires location services to work</string>

<key>NSLocationAlwaysUsageDescription</key>
<string>This application requires location services to work</string>

我认为这些条目可能自我在Xcode 5中开始此项目以来就一直缺失。我猜测Xcode 6可能会为这些键添加默认条目,但尚未确认。

您可以在此处找到有关这两个设置的更多信息。


13
默认情况下,Xcode 6不会自动添加这些键,如果您打算实现CLLocationManager,必须手动添加它们。 - Wesley Smith
1
我也不得不手动添加它们,无法通过Xcode6中info.plist的RawValues视图添加! - Kendall Helmstetter Gelner
1
我找了好久才找到这个。我的 [CLLLocationManager authorizationStatus] 消息只返回一个 <nil> 值。在添加了上述密钥后,它开始返回相应的消息输出枚举。根据我的发现,这是 iOS 8 中的一个已知错误,但是在很长一段时间内我都没有在我的上下文中找到任何东西。感谢你! - MrOli3000
4
我喜欢你放入的字符串值。 - Chris Chen
1
该消息将出现在警报的子消息中,询问用户是否想共享他们的位置。因此,在我的应用程序中,简单地为空(空白)似乎是最合理的选择。解释为什么要使用位置也是有道理的。但是,对我来说,NSLocationWhenInUseUsageDescription的行为不像预期的那样(即使返回值正确,仍然会被拒绝)。NSLocationAlwaysUsageDescription可以正常工作。 - arcady bob
显示剩余5条评论

105

为了确保向后兼容iOS 7,您应该检查用户是否正在运行iOS 8或iOS 7。例如:

#define IS_OS_8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)

//In ViewDidLoad
if(IS_OS_8_OR_LATER) {
   [self.locationManager requestAlwaysAuthorization];
}

[self.locationManager startUpdatingLocation];

164
比检查版本号更好的方法是检查对象是否有该选择器,例如:if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { ... - progrmr
1
在使用Xcode 5进行构建时,我只是实现@progrmr的解决方案遇到了麻烦,因为它不知道requestAlwaysAuthorization方法是什么。我的附加解决方案是在'if'语句中使用这行代码,而不是正常的方法调用:[self.locationManager performSelector:@selector(requestAlwaysAuthorization)]; 也许对其他人来说这很明显,但我花了一段时间才想出来。我认为这是正确的解决方案,直到最终的Xcode 6发布。 - VinceFior
2
正确的解决方案是使用Xcode 6进行构建。如果您正在使用Xcode 5进行构建,则无需请求授权,它是自动的。 - progrmr
你说得有道理。我的担心是我想让我的代码在Xcode 5中编译(尽管除非在Xcode 6中编译,否则它在iOS 8上无法工作),而我的团队正在过渡到Xcode 6。但也许更好的工作流程是正常编写代码,直到我们转移到Xcode 6再合并。谢谢。 - VinceFior
@VinceFior 建议的方法是使用SDK定义的宏__IPHONE_OS_VERSION_MAX_ALLOWED 进行条件编译 (#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000)。 - Steven Kramer
显示剩余3条评论

51
- (void)startLocationManager
{
    locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;
    locationManager.distanceFilter = kCLDistanceFilterNone; //whenever we move
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;

    [locationManager startUpdatingLocation];
    [locationManager requestWhenInUseAuthorization]; // Add This Line


}

关于您的info.plist文件 此处插入图像描述


1
在以下网址中找到了更详细的类似解决方案:http://datacalculation.blogspot.in/2014/11/how-to-fix-cllocationmanager-location.html - iOS Test
2
根据苹果开发文档,可以在应用程序的授权状态确定之前安全地启动位置服务。虽然可以启动位置服务,但这些服务在授权状态更改为 kCLAuthorizationStatusAuthorizedAlways 或 kCLAuthorizationStatusAuthorizedWhenInUse 之前不会传递任何数据。 - Andrew
有点晚了,但我建议您隐藏 plist 文件中无关的部分,特别是 FacebookAppID。 - Przemysław Wrzesiński
谢谢。但那只是一个虚拟的示例 :) - neo D1
同样重要的是,项目指向正确的info.plist文件(其中定义了关键字)。这是在项目构建设置中确定的,位于Info.plist文件下。 - clearlight

30

根据苹果文档:

从iOS 8开始,你的应用程序的Info.plist文件中必须包含一个NSLocationWhenInUseUsageDescriptionNSLocationAlwaysUsageDescription键值。在注册位置更新之前,需要从用户那里请求权限,可以通过调用[self.myLocationManager requestWhenInUseAuthorization][self.myLocationManager requestAlwaysAuthorization]来实现,取决于您的需求。然后,在随后的对话框中将显示您输入到Info.plist中的字符串。

如果用户授权,则按照通常的方式运行。如果他们拒绝授权,则不会通知委托进行位置更新。


我在这里找到了简单的描述:http://datacalculation.blogspot.in/2014/11/how-to-fix-cllocationmanager-location.html - iOS Test

28
- (void)viewDidLoad
{
    
    [super viewDidLoad];
    self.locationManager = [[CLLocationManager alloc] init];
    
    self.locationManager.delegate = self;
    if([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]){
        NSUInteger code = [CLLocationManager authorizationStatus];
        if (code == kCLAuthorizationStatusNotDetermined && ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)] || [self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)])) {
            // choose one request according to your business.
            if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]){
                [self.locationManager requestAlwaysAuthorization];
            } else if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]) {
                [self.locationManager  requestWhenInUseAuthorization];
            } else {
                NSLog(@"Info.plist does not contain NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription");
            }
        }
    }
    [self.locationManager startUpdatingLocation];
}

>  #pragma mark - CLLocationManagerDelegate

    - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
    {
        NSLog(@"didFailWithError: %@", error);
        UIAlertView *errorAlert = [[UIAlertView alloc]
                                   initWithTitle:@"Error" message:@"Failed to Get Your Location" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [errorAlert show];
    }
    
    - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
    {
        NSLog(@"didUpdateToLocation: %@", newLocation);
        CLLocation *currentLocation = newLocation;
        
        if (currentLocation != nil) {
            longitudeLabel.text = [NSString stringWithFormat:@"%.8f", currentLocation.coordinate.longitude];
            latitudeLabel.text = [NSString stringWithFormat:@"%.8f", currentLocation.coordinate.latitude];
        }
    }
在iOS 8中,要使位置服务正常工作,您需要完成两个额外的步骤:向Info.plist添加一个键,并请求位置管理器授权以开始工作。新的位置授权有两个Info.plist键,其中一个或两个都是必需的。如果这些键都不存在,则可以调用startUpdatingLocation,但位置管理器实际上不会启动。它也不会向代表发送失败消息(因为它从未启动,所以无法失败)。如果添加了一个或两个键但忘记显式请求授权,它也会失败。因此,您需要做的第一件事是将以下一个或两个键添加到Info.plist文件中:

  • NSLocationWhenInUseUsageDescription
  • NSLocationAlwaysUsageDescription

这两个键都需要一个字符串,该字符串描述了您需要位置服务的原因。您可以输入类似于“需要定位才能确定您的位置”这样的字符串,就像在iOS 7中一样,可以在InfoPlist.strings文件中本地化。

enter image description here


19

我在Xcode 5中编写了一份解决方案:

#ifdef __IPHONE_8_0
    NSUInteger code = [CLLocationManager authorizationStatus];
    if (code == kCLAuthorizationStatusNotDetermined && ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)] || [self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)])) {
        // choose one request according to your business.
        if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]){
            [self.locationManager requestAlwaysAuthorization];
        } else if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]) {
            [self.locationManager  requestWhenInUseAuthorization];
        } else {
            NSLog(@"Info.plist does not contain NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription");
        }
    }
#endif
    [self.locationManager startUpdatingLocation];

2
不错的动态解决方案,适用于所有应用程序。我会改变顺序,而不是检查 iPhone 8.0,我会检查位置管理器是否响应授权,然后运行 authorizationstatus 来获取代码。这将使其更加通用。 - zirinisp
同样也在 xCode 7.2 上工作过。 - Totoro

17

旧的请求位置的代码在iOS 8中无法工作。您可以尝试使用此位置授权方法:

- (void)requestAlwaysAuthorization
{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];

    // If the status is denied or only granted for when in use, display an alert
    if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status ==        kCLAuthorizationStatusDenied) {
        NSString *title;
        title = (status == kCLAuthorizationStatusDenied) ? @"Location services are off" :   @"Background location is not enabled";
        NSString *message = @"To use background location you must turn on 'Always' in the Location Services Settings";

        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
                                                            message:message
                                                           delegate:self
                                                  cancelButtonTitle:@"Cancel"
                                                  otherButtonTitles:@"Settings", nil];
        [alertView show];
    }
    // The user has not enabled any location services. Request background authorization.
    else if (status == kCLAuthorizationStatusNotDetermined) {
        [self.locationManager requestAlwaysAuthorization];
    }
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1) {
        // Send the user to the Settings for this app
        NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
        [[UIApplication sharedApplication] openURL:settingsURL];
    }
}

UIAlertView已被弃用。您应该改用UIAlertController。 - Pasan Premaratne
是的,我知道它已经被弃用了,但这也可以工作。但是,对于iOS8最好使用UIAlertController。 - Nits007ak

13
在iOS 8中,要让位置服务正常工作,你需要做两件额外的事情:将一个密钥添加到你的Info.plist文件中,并向位置管理器请求授权以启动。

info.plist:

<key>NSLocationUsageDescription</key>
<string>I need location</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>I need location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>I need location</string>

将这段代码添加到你的程序中

if (IS_OS_8_OR_LATER)
{
    [locationmanager requestWhenInUseAuthorization];

    [locationmanager requestAlwaysAuthorization];
}

1
你的伪代码是预处理器调用还是常规调用并不清楚。 - El Dude
1
将以下内容添加到您的constants.h或.pch文件中:#define IS_OS_8_OR_LATER([[UIDevice currentDevice] .systemVersion floatValue]> = 8.0) - OxenBoxen
为什么要同时请求两个,你应该添加一条注释,根据你的应用需求只使用其中一个。 - powerj1984
info.plist 部分在 Unity3d 中很容易实现,但如果使用 Unity3d,如何添加代码部分呢?应该放在哪里? - CthulhuJon

11

Swift开发者常见错误:

首先,请确保您为NSLocationWhenInUseUsageDescriptionNSLocationAlwaysUsageDescription之一的属性添加了一个值。

如果您仍然没有看到弹出窗口要求授权,请查看是否在您的视图控制器的viewDidLoad方法中放置了var locationManager = CLLocationManager()这一行代码。如果是这样,则即使您调用locationManager.requestWhenInUseAuthorization(),也不会显示任何内容。这是因为在执行完viewDidLoad后,locationManager变量被释放(清除)。

解决方案是将var locationManager = CLLocationManager()代码行放置在类方法的顶部。


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