iOS-使用CoreLocation定位

要使用Core Location首先我们要先了解CLLocation、CLLocationManager、CLLocationManagerDelegate等相关概念。这些是构成我们获取设备定位,朝向,速度等数据的基本类。

CLLocation

  一个CLLocation对象里存储了由CLLocationManager对象产生的数据。其中的数据包括地理坐标(经纬度坐标)和海拔高度。用户可以通过设定测量精度来获取需要的数据。在iOS设备中这个类还可以获取设备速度和朝向等信息。
  通常情况下我们会使用CLLocationManager生成的实例来获取设备当前位置。然而,如果你想缓存某些定位数据和计算两点间距离等的话,可以通过创建一个自定义的实例来实现。

  首先我们先来看一下这个类存储了一些什么数据。其中主要的包括CLLocationAccuracy、CLLocationCoordinate2D、CLLocationDegrees、CLLocationDirection、CLLocationDistance、CLLocationSpeed。要使用这些类型,都要先引入CoreLocation框架。

@import CoreLocation;

CLLocationAccuracy

一个类型表示测量的精度。

//最佳测量精度
extern const CLLocationAccuracy kCLLocationAccuracyBest;
//精确度十米以内
extern const CLLocationAccuracy kCLLocationAccuracyNearestTenMeters;
//精确度一百米以内
extern const CLLocationAccuracy kCLLocationAccuracyHundredMeters;
//精确度一千米以内
extern const CLLocationAccuracy kCLLocationAccuracyKilometer;
//精确度三千米以内
extern const CLLocationAccuracy kCLLocationAccuracyThreeKilometers;

CLLocationCoordinate2D

一个包含了地理坐标(经度和纬度)的结构体。

CLLocationDegrees latitude; //纬度
CLLocationDegrees longitude; //经度

CLLocationDegrees

表示一个经度或纬度值。实际上就是一个double类型的值。

typedef double CLLocationDegrees

CLLocationDirection

表示相对于现实世界北方的方向的值。也是一个double类型的值。

typedef double CLLocationDirection

CLLocationDistance

距离值(单位:米)。double类型的值。

typedef double CLLocationDistance

CLLocationSpeed

设备每秒移动的速度。double类型的值。

typedef double CLLocationSpeed

  了解了这个类的相关数据结构之后,我们来接着看看可以使用这个类来做一些什么事。通过查阅稳当我们发现这个类还可以实现的最主要的功能就是计算两点间的距离,尤其是计算设定的某一点和当前设备位置之间的距离。

//返回当前设备距离设定的位置之间的距离
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location

CLLocationManager

一个location manager对象可以提供一下相关的定位服务:
(1)追踪用户当前位置的变化。
(2)从罗盘中获取朝向信息。(只有iOS可用)
(3)监控特定区域,当用户进入或离开该区域时同时生成位置信息。
(4)设置用户位置更新在后台运行。
(5)查找附近的beacons设备。

一下是配置一个location manage,并使用它的步骤:
(1)总是请求用户授权定位服务,并确认需要启用的服务正确获取授权。
(2)创建一个CLLocationManager类的实例,并存储为强引用变量。因为大多数的定位任务都是异步执行的,以局部变量的方式存储location manager对象是非常低效的。
(3)分配一个特定的delegate对象。这个对象需要遵循CLLocationManagerDelegate协议。
(4)根据需要的服务配置一些额外的属性。
(5)调用方法启动定位服务。

获取权限

- (void)requestWhenInUseAuthorization

这个方法是异步的,并且会及时的提示用户授权定位服务的权限。另一个方法可以调用以下方法获取权限。

- (void)requestAlwaysAuthorization

当用户授权了“Always”权限后,你的App可以在前后台中使用任何的定位服务。此外,那些允许你的应用在后台执行的服务也可以继续运行。

接下来介绍几个常用的启动特定服务的方法。

启动标准定位服务

- (void)startUpdatingLocation

这个方法会立即返回结果。调用这个方法会使得Location对象生成一个定位器,然后会通知委托对象,回调其locationManager:didUpdateLocations:消息。此后,当设备移动的距离超过设定的distanceFilter属性值时,接收器会再次生成一条更新消息。

- (void)stopUpdatingLocation

当不再需要接受定位信息的时候可以调用这个方法。如果需要再次使用定位服务,可再次调用startUpdatingLocation方法。

- (void)requestLocation

这个方法和startUpdatingLocation类似,也会将获取的定位信息传递给委托对象的locationManager:didUpdateLocations:消息。而不同的是,这个方法只产生一次定位信息,在此之后定位服务就停止了。当使用这个方法时,委托对象必须要实现locationManager:didUpdateLocations:locationManager:didFailWithError:方法。

后面还有类似于特定的定位服务,设备朝向服务、区域监听等服务,可以自行查阅开发者文档中的说明。

CLLocationManagerDelegate

  CLLocationManagerDelegte对象是定义来接收来自CLLocation对象传递来的设备定位和朝向信息的更新的。在成功获取定位和朝向更新信息后,你可以在用户交互界面进行一些更新操作。
  委托对象的方法会在你启动定位服务的同一个线程中被调用。因此,那个线程必须是处于激活的循环状态(active run loop),比如说应用的主线程。
  由于我们上面主要说的是定位服务,所以接下来介绍的方法中主要是和定位相关的,其他的更多方法可以查阅文档。

- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray<CLLocation *> *)locations

当有新的可用定位信息是会被调用。manager参数为产生定位数据的location对象。locations中存储了传递过来的定位数据。这个数组通常包含至少一个定位数据。如果更新被延时或多个定位数据在被传递之前通知到达,则会导致该数组中包含多个定位信息。但是这个数组中的数据存储是有序的,所以最新的定位数据一定是最后一个定位数据对象!!!

- (void)locationManager:(CLLocationManager *)manager
       didFailWithError:(NSError *)error

这个方法是可选的,但是官方文档中推荐尽量实现该方法。
这个方法会在location对象尝试获取定位和朝向信息并发生错误时调用。如果定位服务无法马上获取一个定位信息,则会返回一个kCLErrorLocationUnknown错误并继续尝试。在这种情况下,你可以简单的忽略该错误信息,并等待下一个定位信息的到来。如果因为附近的强磁场干扰导致无法获取朝向信息,则会返回kCLErrorHeadingFailure错误。
如果用户拒绝你的应用使用定位服务,这个方法则会返回kCLErrorDenied错误。如果接收到这样一个错误信息,那么你就应该停止继续使用定位服务。

Demon

//
//  ViewController.m
//  CoreLocationTest
//
//  Created by lysongzi on 16/1/24.
//  Copyright © 2016年 lysongzi. All rights reserved.
//

#import "ViewController.h"

#import <CoreLocation/CoreLocation.h>

@interface ViewController () <CLLocationManagerDelegate>

@property (strong, nonatomic) CLLocationManager *manager;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted
        || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied)
    {
        NSLog(@"应用不可以使用定位服务.");
        return;
    }

    [self initLocation];

    //如果是IOS请求获取权限        
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined)
    {
        //请求获取用户定位服务权限
        [self.manager requestWhenInUseAuthorization];
        NSLog(@"请求定位服务.");
    }

    //启动定位服务
    [self.manager startUpdatingLocation];
    NSLog(@"开始定位服务.");
}

- (void)initLocation
{
    //懒加载
    if (!self.manager)
    {
        self.manager = [[CLLocationManager alloc] init];
        self.manager.delegate = self;
        self.manager.desiredAccuracy = kCLLocationAccuracyBest;
        self.manager.distanceFilter = 10;
    }
}

#pragma mark CLLocationManagerDelegate委托实现

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
    //获取最新定位信息
    CLLocation *location = [locations lastObject];

    if (location.horizontalAccuracy > 0)
    {
        NSLog(@"最新位置:%f %f, 海拔高度:%f", location.coordinate.latitude, location.coordinate.longitude, location.altitude);

        //定位成功,关闭定位服务
        [self.manager stopUpdatingLocation];
    }
}

-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    if ([error code] == kCLErrorLocationUnknown) {
        NSLog(@"获取定位失败.");
    }
    else if ([error code] == kCLErrorHeadingFailure){
        NSLog(@"获取朝向信息失败.");
    }
    else if ([error code] == kCLErrorDenied){
        NSLog(@"用户拒绝访问定位服务.");
    }
}

@end

然后我们会获取响应的位置信息:

2016-01-28 17:05:54.955 CoreLocationTest[460:64113] 开始定位服务.
2016-01-28 17:05:54.972 CoreLocationTest[460:64113] 最新位置:21.478879 109.101213, 海拔高度:21.719454

参考资料

  1. 苹果开发者官方文档
  2. iOS项目开发实战——使用CoreLocation获取当前位置信息