CoreLocation 教程

CoreLocation - iOS定位

使用 CoreLocation 框架定位,基本分为权限获取,位置更新,区域检测,iBeacon,方向检测,地理编码几方面。

demo链接: Location_Demo

iOS的定位支持

GPS卫星定位

其原理就是:利用天上的卫星(24颗工作卫星和数颗备份星)不断地广播信号, 地面的GPS接收设备收到信号后, 通过分析多个卫星信号,就可以计算出地球坐标, GPS保证全球任何一个地方(98%)都可以同时收到至少4个卫星的信号, 从而可以准确确定您的经纬度以及海拔位置(三颗星只能获得经纬度,四颗星还可以获得海拔高度)。GPS定位精度可达10米以内

iOS的GPS定位于单纯的GPS定位不同,它是A-GPS(所谓辅助GPS),首先通过基站定位或WiFi定位获得该设备的大概位置,然后通过将设备的大致位置发发哦远程服务器,由服务器负责进行查询和计算,从而获取当前位置的卫星信息,并通过网络将这些卫星信息反馈给iOS设备,避免了iOS设备直接通过GPS扫面、分析天上的卫星信息。A-GPS优点是定位快,缺点是需要网络,但也只是在初次定位时需要网络,因为一旦卫星信息返回,在有限时间和范围内,这些信息无须改变,之后的GPS定位就不再需要联网,都是直接用这些卫星参数接受信息了。

与基站、WIFI定位相比,GPS定位耗电量最大,速度最慢,但是精度最高。

基站定位

每个手机基站都是一个标识符,iOS设备可以搜集周围所有收到信号的基站和它们的标识符,通过联网发送到苹果云端服务器,再由服务器根据这些基站的位置信息查询并计算出当前位置,然后把该定位信息返回给手机。

WiFi定位

iOS设备通过无线网卡搜集周围所有的WiFi热点,获得它们的MAC地址,然后通过Apple的云端服务器查询该WiFi热点是否已经登记,如果已经登记,即可获取该WiFi热点的位置,最后通过对多个WiFi热点折中计算得到当前位置并返回给iOS设备。精确度大概在几十米范围内。

定位相关API

首先需要获取地理信息权限。定位权限分为 仅在使用应用期间始终允许 ; 获取到权限后,使用 CLLocationManager 类可以开始在其代理方法 didUpdateLocations 中获得CLLocation位置信息。

1
2
3
4
5
6
7
//CLLocationManager的常用操作
//开始用户定位
- (void)startUpdatingLocation;
//停止用户定位
- (void) stopUpdatingLocation;
//当调用了startUpdatingLocation方法后,就开始不断地定位用户的位置,中途会频繁地调用代理的下面方法
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定位设置:
//只有当最新的位置与上一次获取的位置之间的距离, 大于这个值时, 才会通过代理告诉外界.
self.locationM.distanceFilter = 100;

/*
定位精确度
kCLLocationAccuracyBestForNavigation 最适合导航
kCLLocationAccuracyBest 精度最好的
kCLLocationAccuracyNearestTenMeters 附近10米
kCLLocationAccuracyHundredMeters 附近100米
kCLLocationAccuracyKilometer 附近1000米
kCLLocationAccuracyThreeKilometers 附近3000米
*/
self.locationM.desiredAccuracy = kCLLocationAccuracyBest;

可以获得的 CLLocation 位置信息包括了:经纬度、位置的精度、海拔高度、海拔高度的精度、速度等信息。

相应的通过CLLocation 可以计算两地之间的直线距离:

1
2
3
4
5
6
7
8
// 北京:39.6 116.39
// 广州:23.08 113.15
CLLocation *BeiJing = [[CLLocation alloc] initWithLatitude:39.6 longitude:116.39];
CLLocation *GuangZhou = [[CLLocation alloc] initWithLatitude:23.08 longitude:113.15];

// 得到两地之间的距离
CLLocationDistance distance = [BeiJing distanceFromLocation:GuangZhou];
NSLog(@"%.2f", distance);

后台定位

后台短时间持续定位

可以在 Background Modes 中开启Location Updates选项。这样就可以实现后台定位,但是该方法只能实现后台定位20-30分钟的时间。

后台永久持续定位

设置代码如下:

1
2
3
4
5
6
7
8
9
10
//allowsBackgroundLocationUpdates 这个属性是 iOS 9.0 新增的属性,
//默认为 NO ,设置为 YES
//需要保证 Target -> Capabilities -> Background Modes 的开关打开,并勾选 Location updates ,否则会导致 crash。
if (@available(iOS 9.0, *)) {
self.locationManager.allowsBackgroundLocationUpdates = YES;
} else {
// Fallback on earlier versions
}
//系统是否可以自行中断程序的定位功能
self.locationManager.pausesLocationUpdatesAutomatically = NO;

该方法让系统不能够自行关闭程序的定位功能,保证程序一直处于后台定位中,

定位权限为应用使用期间的时候,程序在后台运行时会在顶部有一条蓝色的信息框
定位权限为始终的时候,就不会有蓝色框了。

关于 pausesLocationUpdatesAutomatically 属性

CLLocationManager.pausesLocationUpdatesAutomatically 设置是否允许自动暂停定位服务,目前Apple的官方文档描述的很模糊,导致Stackoverflow上有很多的讨论:

该选项主要用来节省电量消耗,防止app在进入后台后没有及时停止定位消耗电量,设置为YES后,当iOS判定设备不太可能发生移动的时候(这句话是Apple文档直译,然而并不知道具体怎么判定),会自动暂停掉定位服务以及相关硬件设备
app在后台定位时,一旦符合了Apple的判定,定位服务以及相关硬件设备被暂停了,除非app回到前台,否则不会恢复定位服务(硬件暂停使得iOS无法判定设备移动)

区域监听

区域监听是指,我们通过CLCircularRegion指定一个区域, 然后当用户持握设备进入或者离开指定区域, 我们都能监听到.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//初始化CLLocationManager等……
// 1. 判断区域监听服务是否可用
if ([CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]])
{

// 创建区域中心
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(29.12345, 131.23456);
// 创建区域(指定区域中心,和区域半径)
CLLocationDistance radius = 1000;
// 判断区域半径是否大于最大监听区域半径,如果大于, 就没法监听
if (radius > self.locationM.maximumRegionMonitoringDistance) {
radius = self.locationM.maximumRegionMonitoringDistance;
}
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:radius identifier:@"一个identifier"];

// 开始监听指定区域
[self.locationM startMonitoringForRegion:region];
}
else{NSLog(@"区域监听不可用");}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 进去监听区域后调用 
-(void)locationManager:(nonnull CLLocationManager *)manager didEnterRegion:(nonnull CLRegion *)region
{
NSLog(@"进入区域---%@", region.identifier);
[manager stopMonitoringForRegion:region];
}

// 离开监听区域后调用
-(void)locationManager:(nonnull CLLocationManager *)manager didExitRegion:(nonnull CLRegion *)region
{
NSLog(@"离开区域---%@", region.identifier);
[manager stopMonitoringForRegion:region];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//监听某个区域时, 只有进入或者离开这个区域时, 才能回调对应的方法, 是一个进入或者离开的动作 
//如果想知道某一个区域的当前状态(识别用户是在区域内部, 还是区域外部), 则需要使用以下方法

[self.locationM requestStateForRegion:region];

// 请求某个区域状态时, 回调的代理方法
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
switch (state) {
case CLRegionStateUnknown:
NSLog(@"未知状态");
break;
case CLRegionStateInside:
NSLog(@"在区域内部");
break;
case CLRegionStateOutside:
NSLog(@"在区域外部");
break;
default:
break;
}
}

ibeacon

iBeacon 是苹果公司2013年9月发布的移动设备用OS(iOS7)上配备的新功能。其工作方式是,配备有低功耗蓝牙(BLE)通信功能的设备使用BLE技术向周围发送自己特有的 ID,接收到该 ID 的应用软件会根据该 ID 采取一些行动。

iOS 中 iBeacon 是基于地理位置的微定位技术,虽然借助手机蓝牙进行接收Majro、Minor,但是他们在开发工程中没有任何关系。
iBeacon使用苹果提供CoreLocation库,然而在 BLE 在开发过程中使用CoreBluetooth库。从上面提供的库来看就很清楚了,特别是在 iOS8.0 之后的时候如果想使用iBeacon,必须让用户点击是否允许XXapp使用地理位置。如果在第一次使用 iOS App 扫描iBeacon的时候没有提示这句话,是不可能接收到iBeacon的信号(除非iOS 8.0之下)。如果是 BLE 则的开发过程中之需要提示用户打开蓝牙,并不要求其他的地理位置任何信息。

就不写代码了……

地理编码与反地理编码

需要获得网络权限

地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)。

输入上海市闵行区南华街25号的结果:

1
2
3
4
5
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:address
completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
//获取结果
}];

反地理编码:根据给定的经纬度,获得具体的位置信息,省市区街道等信息;

解析定位当前的位置的结果:

1
2
3
4
5
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location
completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
//获取结果
}];

1
2
3
4
5
6
7
8
9
CLPlacemark 地标对象详解
location : CLLocation 类型, 位置对象信息, 里面包含经纬度, 海拔等等
region : CLRegion 类型, 地标对象对应的区域
addressDictionary : NSDictionary 类型, 存放街道,省市等信息
name : NSString 类型, 地址全称
thoroughfare : NSString 类型, 街道名称
locality : NSString 类型, 城市名称
administrativeArea : NSString 类型, 省名称
country : NSString 类型, 国家名称

方向监测 / 指南针

1
2
3
4
5
6
7
8
9
10
11
//获取指南针信息
//①、CLLocationDegrees headingFilter:设置只有当设置方向的改变值超过该属性值时才激发delegate方法;
//②、CLDeviceOrientation headingOrientation:设置设备当前方向;
[self.locationManager startUpdatingHeading];
//当设备的方向改变时,iOS系统将会自动激发CLLocationManager的delegate对象的
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
//CLHeading方法的主要参数:
//与磁北方向的偏角@property(readonly, nonatomic) CLLocationDirection magneticHeading;
//与正北方向的偏角@property(readonly, nonatomic) CLLocationDirection trueHeading;

}

方向补充:

Core Location支持两种不同的获取方向相关信息的方式:

当设备处于某个点时(CLHeading),带有磁力计的设备能能够报告其位置信息;此类被称为设备的朝向。
当设备处于移动过程中时(CLLocation.course),通过带有GPS硬件的报告的位置位置信息;此类被称为设备的行动方向。

朝向和行动方向表现出设备不同的信息。设备的朝向真实的反映出了设备在一个点时相对于磁北或者真北的朝向。而设备的行动方向则表现了设备在运动过程中的运动方向其并没有考虑设备的方向。你使用一种还是这两种相结合,完全取决于你的APP;例如:一个导航APP是否要在两者之间切换完全取决于用户的速度,在步行速度时,设备的朝向对于在当前环境下的确认用户方向是用户最为关心的事情;而在一辆车里运动时,行动方向则变得尤为重要。


后台定位补充:

关于 pausesLocationUpdatesAutomatically 属性:

CLLocationManager.pausesLocationUpdatesAutomatically 设置是否允许自动暂停定位服务,目前Apple的官方文档描述的很模糊,导致Stackoverflow上有很多的讨论

该选项主要用来节省电量消耗,防止app在进入后台后没有及时停止定位消耗电量,设置为YES后,当iOS判定 设备不太可能发生移动的时候 (这句话是Apple文档直译,然而并不知道具体怎么判定),会自动暂停掉定位服务以及相关硬件设备

唤醒:

首先这个方法需要 始终 权限,然后在开启定位调用 startUpdatingLocation 的同时调用 startMonitoringSignificantLocationChanges 方法。

1
2
3
[_locationManager startUpdatingLocation];
// 开启位置明显变化监控
[_locationManager startMonitoringSignificantLocationChanges];

开启了这个方法之后,当位置发生较明显的变化时同样会触发一个位置更新事件,这个变化距离不依据 distanceFilter 属性值变化,而是固定的 500 米(指与上一次触发此事件时的距离),且每 5 分钟最多触发一次,触发的时候如果 APP 被杀死了,这个事件还会唤醒 APP ,APP 会在用户无感知的情况下重新走正常的启动流程,只是界面上没有任何显示,此时会在 application:didFinishLaunchingWithOptions: 方法中携带 UIApplicationLaunchOptionsLocationKey 这个 key 来表明当前启动是被位置事件唤醒的,请注意判断这个 key 来绕过一些不需要在后台启动时执行的逻辑,或者执行一些你想要在后台启动后执行的逻辑,比如启动位置上报服务。在 APP 重新启动之后需要重新调用 startMonitoringSignificantLocationChanges 以保证 APP 再次被杀之后能被重新唤醒。

由此方法触发的位置更新事件同样会调用正常位置更新服务 startUpdatingLocation 的locationManager:didUpdateLocations: 回调方法,这个更新事件可能是一个最近缓存的位置,也可能是最新位置,而更精确的位置会在几秒钟之后再次回调,所以如果对细微位置差别比较敏感的话,可以通过回调的 CLLocation 对象中的时间戳来过滤。