我在哪? Core Location告诉你

CoreLocationLearn_170621_intro

我在哪?我是谁?我要去哪?

定位简述

设备通过AGPS、蓝牙(iBeancon)以及WiFi进行定位。

CoreLocationLearn_170621_00

  • GPS 在搜星条件比较好的情况下精度在10米~20米左右
  • 基站定位 主要看基站密度,一般在百米~数公里
  • AGPS 利用手机基站信号结合GPS卫星信号,加快定位速度
  • 蓝牙定位 主要应用于室内定位,利用蓝牙设备持续发送的广播帧实现
  • WiFi定位 利用扫描附近wifi时获取到的多个mac地址与服务器中的mac地址库匹配,加上信号强弱计算从而定位,服务器中的mac地址多数为地图街景车扫描到的

初窥

CoreLocationLearn_170621_01

类名 用途
CLAvailability 定位可用性(不需开发来引用进行使用)
CLErrorDomain、CLError 报错Domain和type声明
CLRegion
CLCircularRegion
CLBeaconRegion
位置范围
CLHeading 朝向信息
CLLocation 位置信息
CLLocationManager 定位管理类
CLLocationManagerDelegate 定位管理类协议
CLLocationManager+CLVisitExtensions 定位管理类扩展方法
CLPlacemark 反地理编码信息
CLGeocoder (反)地理编码
CLVisit 访问位置信息

基本使用

定位权限

iOS 8之前

  1. 前台定位不需要通过特定方法请求权限
  2. 后台定位需要在target配置-Capabilities-Background Modes开启,并勾选Location updates。

CoreLocationLearn_170621_02

iOS 8之后

需要在info.plist中设置:

  • 前台定位: NSLocationWhenInUseUsageDescription
  • 后台定位: 前台定位基础上 + NSLocationAlwaysUsageDescription

CoreLocationLearn_170621_03

  1. 前台定位需要通过CLLocationManager的对象方法-requestWhenInUseAuthorization请求获取权限
  2. 后台定位
    有一下两种方法,前一种在App进入后台后,状态栏会展示蓝色条(“xxx”正在使用使用您的位置信息),后一种不会。
    • 在前台定位基础上,在Background Modes中勾选Location updates
    • 通过CLLocationManager的对象方法-requestWhenInUseAuthorization请求获取前台定位权限

iOS 11之后

需要在info.plist中设置:

  • 前台定位: 与iOS8一样
  • 后台定位: 在iOS8基础上增加NSLocationAlwaysAndWhenInUseUsageDescription

CoreLocationLearn_170621_03-01

权限检查

  1. 通过CLLocationManager的类方法检查定位服务开启以及定位授权情况

    1
    2
    3
    4
    5
    //获取定位服务是否开启
    BOOL enable = [CLLocationManager locationServicesEnabled];

    //定位授权情况
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
  2. 通过CLLocationManager的代理方法,监听授权变化情况

    1
    2
    3
    - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    NSLog(@"%ld", status);
    }
权限状态 说明
kCLAuthorizationStatusNotDetermined 用户还未决定是否使用定位
kCLAuthorizationStatusRestricted 定位服务授权状态是受限制
(可能不是用户拒绝的)
kCLAuthorizationStatusDenied 用户拒绝授权
设置中定位服务关闭
kCLAuthorizationStatusAuthorizedAlways 允许任何时候定位
kCLAuthorizationStatusAuthorizedWhenInUse 允许App运行时定位
kCLAuthorizationStatusAuthorized 已废弃,等同
kCLAuthorizationStatusAuthorizedAlways

前台定位

使用CLLocationManager的对象方法-startUpdatingLocation获取位置信息,通过其代理方法获取定位信息与错误信息,定位会根据配置的精度和更新距离自行处理更新位置信息的频率,鉴于定位对电量的消耗,注意在适当的时候调用-stopUpdatingLocation进行停止。

  1. 进行前台定位
    可以通过对象属性desiredAccuracy设置精度,distanceFilter设置触发回调更新的距离

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    self.manager = [[CLLocationManager alloc] init];
    self.manager.delegate = self;

    //配置精度
    //定位精度,精度高,使用点多,默认为kCLLocationAccuracyBest
    self.manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;

    //地图上每隔多少米才更新一次,低于这个距离的定位不会回调delegate
    self.manager.distanceFilter = 200;

    //进行定位
    //持续请求,需要在合适的时候使用-stopUpdatingLocation停止
    [self.manager startUpdatingLocation];
  2. 回调定位结果
    通过locationManager:didUpdateLocations:获取定位结果信息,位置信息是一个数组,其可能没有或包含一个或者多个CLLocation位置信息对象,结合App用途实际情况进行使用。
    通过locationManager:didFailWithError:获取定位失败的错误信息,error相关的类型声明在CLError以及CLErrorDomain中可以看到。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //定位结果回调
    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(nonnull NSArray<CLLocation *> *)locations {
    NSLog(@"回调位置数组-->%@\n",locations);
    }

    //定位失败回调
    - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
    NSLog(@"定位失败错误信息:%@\n", error);
    }

补充
iOS 9新增了定位方法-requestLocation,此方法会以精度从低到高进行请求定位,以获取到精度最高的位置信息,通过代理方法返回位置信息。
如果最终获得到的位置信息精度不够高的话,依然会在超时后通过代理方法返回位置信息。如果未获得到位置信息,会通过失败代理进行回调(使用此方法定位必须实现失败回调)。
此方法只会回调一次定位结果,不需要使用-stopUpdatingLocation进行停止。
注意,如果与-startUpdatingLocation同时使用,本方法的定位请求会被取消。

后台定位

主要是请求用户权限上的区别,定位方法依然是使用-startUpdatingLocation开启定位,然后通过代理方法locationManager:didUpdateLocations:获取定位结果信息

在iOS 9后CLLocationManager新增了一个allowsBackgroundLocationUpdates的属性,默认为NO,如果在未设置target配置-Capabilities-开启Background Modes并勾选Location updates的情况下,需要进行后台定位,必须给这个属性设置为YES;若在工程中配置后台模式,此值页推荐是设置为YES,虽然NO的时候影响也不大。

范围监听

通过设定一个/多个地理封闭区域,结合定位信息来判断设备进入/离开所设定的区域,并在这些时候做出一些响应。

  1. 使用CLLocationManager类方法+isMonitoringAvailableForClass:判断是否可以进行监听
  2. 创建CLRegion/CLCircularRegion对象设定一个封闭区域
  3. 使用-startMonitoringForRegion:方法开始监听位置
  4. 实现代理方法获取监听信息

CoreLocationLearn_170621_04

启动监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//判断是否可以监听
if ([CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
//监听区域中心
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(+37.33233141,-122.03121860);

//监听区域的半径
CLLocationDistance distance = 300;

NSLog(@"最大监听范围 %lf", self.manager.maximumRegionMonitoringDistance);

//创建监听区域对象
self.region = [[CLCircularRegion alloc] initWithCenter:coordinate radius:distance identifier:@"devHY"];

//启动监听
[self.manager startMonitoringForRegion:self.region];
} else {
NSLog(@"监听区域不可用");
}

监听回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 进入监听区域回调
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(@"进入区域--> %@", region);
}

// 离开监听区域回调
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(@"离开区域--> %@", region);
}

// 开始监听区域
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
NSLog(@"区域开始监听--> %@",region);
}

// 监听区域失败回调
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSLog(@"区域%@监听失败--> %@",region ,error);
}

也可以通过-requestStateForRegion:方法获取当前定位与某个区域的状态,结果会通过代理方法locationManager:didDetermineState:region:进行回调

方位指向

通过CoreLocation也可以获取到设备的方位,使用CLLocationManager的对象方法-startUpdatingHeading可以实时获取设备方位。
其结果通过代理方法locationManager:didUpdateHeading:进行回调,返回一个CLHeading类的对象。

CLHeading对象主要属性

属性 简述
magneticHeading 磁北极(0~359.9),0为磁北极
trueHeading 真北极(0~359.9),0为真北极
headingAccuracy 方位精度
x 地磁测量的x
y 地磁测量的y
z 地磁测量的z
timestamp 对象产生的时间戳

扩展使用

CoreLocation中也提供了基本的地理编码与反地理编码功能,可以将地址字符串解析为一个CLLocation对象的位置信息,也可将位置信息转义成我们日常使用的文字形式结构化的地址信息以CLPlacemark对象进行呈现。如果需要更多的功能以及地图相关就需要结合MapKit来使用了。

地理编码

地理编码是将统计资料或是地址信息建立空间坐标关系的过程,包含: 正向地理编码、反向地理编码、向量式地理编码、网格式地理编码。在CoreLocation框架中CLGeocoder类提供了正向地理编码反向地理编码两种常用的地理编码功能

正向地理编码

正向地理编码是将地址/地名描述转化为地球表面上相应位置,如输入“北京”,被转化为“中国 北京市 +39.90498900,+116.40528500”

使用CLGeocoder类的以下三种对象方法可以进行正向地理编码,结果会通过block进行回调CLPlacemark对象的数组以及错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//处理地理编码的对象
CLGeocoder *geoCoder = [[CLGeocoder alloc] init];

//使用通讯录获取的地址字典进行编码
[geoCoder geocodeAddressDictionary:addressDict completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

}];

//使用地名字符串进行编码
[geoCoder geocodeAddressString:placeName completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

}];

//使用地名字符串进行编码+限定区域(CLRegion)
[geoCoder geocodeAddressString:placeName inRegion:region completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

}];

反向地理编码

反向地理编码是将地球表面的地址坐标转换为标准地址的过程,如输入“+39.90498900,+116.40528500”,被转化为“中国 北京市 +39.90498900,+116.40528500”

使用CLGeocoder类的对象方法reverseGeocodeLocation:completionHandler:可以进行反向地理编码,结果会通过block进行回调CLPlacemark对象的数组以及错误信息。

1
2
3
4
5
6
7
8
//通过经纬度构建一个CLLocation对象,也可以是通过之前定位获取到的
CLLocation *location = [[CLLocation alloc] initWithLatitude:latitude2 longitude:longtitude2];

//将CLLocation对象反向地理位置编码
CLGeocoder *geoCoder = [[CLGeocoder alloc] init];
[geoCoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
NSLog(@"反向地理编码 -> 结果:%@ \n\t错误:%@\n", placemarks, error);
}];

CLPlacemark

location CLLocation对象
标准的位置信息
region CLRegion对象,范围
timeZone 时区
addressDictionary 可用于保存到通讯录
AddressBookUI
name 地名
thoroughfare 街道名
subThoroughfare 街道相关信息,例如门牌等
locality 城市名
subLocality 城市下属区名
administrativeArea 州,对应中国的省份名称
subAdministrativeArea 其他行政区域信息
postalCode 邮政编码
ISOcountryCode 国家ISO码,如中国CN
country 国家
inlandWater 水源、湖泊
ocean 海洋
areasOfInterest 关联的或利益相关的地标

如:浙江杭州梦想小镇定位反地理编码后结果

CoreLocationLearn_170621_05

补充

  1. 直辖市在之前通过subLocality为nil来判断,但最近接口结果变成了,直辖市的subLocalityadministrativeArea是相同的,并不为会空。
  2. CLGeocoder会根据设备语言进行请求,使返回的CLPlacemark对象的信息是本地化的语言,而且并没有开放接口用于在请求前设置语言。
    如果需要在设置语言为英文的设备上,依然返回中文的CLPlacemark对象信息,可以通过如下方法设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 保存 Device 的现语言 (英语 法语 等)
    NSMutableArray *userDefaultLanguages = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"];
    // 强行设置为中文
    [[NSUserDefaults standardUserDefaults] setObject:@[@"zh-hans"] forKey:@"AppleLanguages"];

    //开始请求反向地理编码
    CLGeocoder *geoCoder = [[CLGeocoder alloc] init];
    [geoCoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
    NSLog(@"反向地理编码 -> 结果:%@ \n\t错误:%@\n", placemarks, error);
    }];

    // 还原Device 的语言(延迟0.5秒,如果直接执行有很大概率依然是原设备语言)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [[NSUserDefaults standardUserDefaults] setObject:userDefaultLanguages forKey:@"AppleLanguages"];
    });

距离计算

通过CLLocation的对象方法distanceFromLocation:可以计算两个Location的距离

1
2
3
4
5
6
//天津loaction
CLLocation *fromLocation = [[CLLocation alloc] initWithLatitude:39.159042 longitude:117.204498];
//北京location
CLLocation *toLocation = [[CLLocation alloc] initWithLatitude:39.904989 longitude:116.405285];
//计算两者的距离
CLLocationDistance distance = [fromLocation distanceFromLocation:toLocation];

Demo

  1. Github - CoreLocationLearn20170531)

参考