一. 简介
在我们日常生活中时常用到地图和定位功能,来导航去你想去的地方或者寻找周边的景点,餐厅,电影院等等,在iOS开发中,要想加入这两大功能,必须基于两个框架进行开发,有了这两个框架,想去哪就去哪。
CoreLocation :用于地理定位,地理编码,区域监听等(着重功能实现)
MapKit :用于地图展示,例如大头针,路线、覆盖层展示等(着重界面展示)
二. CoreLocation框架的基本使用
1. CoreLocation使用步骤
- 导入CoreLocation框架。
- 创建CLLocationManager管理者对象。
- 遵循代理,并实现代理方法。
- 设置获取用户前后台定位授权
- 开始定位。
三. CLLocationManager的使用
学习CLLocationManager可以分为三个部分。1.定位 2.手机朝向 3.区域监听
1. CLLocationManager -- 定位
先通过一个简单例子看一下
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<CLLocationManagerDelegate>
@property(nonatomic,strong)CLLocationManager *locationM;
@end
@implementation ViewController
-(CLLocationManager *)locationM
{
if (_locationM == nil) {
_locationM = [[CLLocationManager alloc]init];
_locationM.delegate = self;
// 只在前台开始定位 修改plist文件 提醒用户
[_locationM requestWhenInUseAuthorization];
// 前后台都可以定位 修改plist文件 提醒用户
// [_locationM requestAlwaysAuthorization];
}
return _locationM;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 开始标准定位服务
[self.locationM startUpdatingLocation];
// 开启显著位置变化定位服务
// [self.locationM startMonitoringSignificantLocationChanges];
}
#pragma mark CLLocationManagerDelegate代理方法
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
// manager 位置管理者 locations 位置数组
// 在这里拿到位置信息做一些处理,这个方法会被持续调
NSLog(@"--开始定位--");
}
注意:
1. 获取前台定位授权[_locationM requestWhenInUseAuthorization];
需要在plist文件中加入NSLocationWhenInUseUsageDescription
。
获取前后台定位授权[_locationM requestAlwaysAuthorization];
需要在plist文件中加入NSLocationAlwaysUsageDescription
。
plist中加入的信息表示获取定位授权时显示的信息。
运行程序请求用户授权时会弹出
2. 开启标准定位服务使用的是GPS/WIFI定位,精确度较高,关闭应用程序就无法获取位置,而开启显著位置变化定位服务使用基站定位(必须有电话模块),当应用程序被关闭时,也可以接受到位置通知,并让app进入后台处理,但是定位精确度没有标准定位服务高,耗电少,定位更新频率依照基站密度而定,只要在基站范围内就显示基站位置,当进入另一个基站范围后更新。
如果要求定位及时,精确度高,并且运行时间短,可以使用标准定位服务。
如果长时间监控用户位置,用户移动速度较快,可使用显著位置变化定位服务
3. 代理方法didUpdateLocations会被持续调用,参数
manager
位置管理者 locations
表示位置数组,里面按照时间先后顺序存储CLLocation
对象,获取最后一个位置信息[locations lastObject]即可
CLLocationManager -- 关于定位属性和方法
// 判断定位功能是否可用
+ (BOOL)locationServicesEnabled
// 设置过滤单位(米)即每隔多少米定位一次
@property(assign, nonatomic) CLLocationDistance distanceFilter;
// 设置定位精确度
@property(assign, nonatomic) CLLocationAccuracy desiredAccuracy;
CLLocationAccuracy kCLLocationAccuracyBestForNavigation // 最适合导航的精确度
CLLocationAccuracy kCLLocationAccuracyBest; // 最好的
CLLocationAccuracy kCLLocationAccuracyNearestTenMeters; // 附近10米范围内
CLLocationAccuracy kCLLocationAccuracyHundredMeters; // 附近100米范围内
CLLocationAccuracy kCLLocationAccuracyKilometer; // 附近1000米范围内
CLLocationAccuracy kCLLocationAccuracyThreeKilometers; // 附近1000米范围内
// 开启定位
- (void)startUpdatingLocation
// 结束定位
- (void)stopUpdatingLocation;
CLLocationManagerDelegate -- 定位常用代理方法
// 定位成功 持续调用
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
// manager : 位置管理者
// locations : 位置数组
// 在这里拿到位置信息做一些处理,这个方法会被持续调
}
// 定位失败时调用
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
// manager : 位置管理者
// error : 错误信息
}
// 当用户定位授权状态发生变化时调用
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
// manager : 管理者
// status :状态
/*
kCLAuthorizationStatusNotDetermined = 0 // 用户未决定时
kCLAuthorizationStatusRestricted // 受限制保留字段
kCLAuthorizationStatusDenied // 被拒绝 1.被拒绝 2.未开启定位服务
kCLAuthorizationStatusAuthorizedAlways // 前后台都可以定位授权
kCLAuthorizationStatusAuthorizedWhenInUse // 前台定位授权
*/
}
CLLocation对象 -- 定位基本属性
// 根据经度和维度创建一个CLLocation对象
- (instancetype)initWithLatitude:(CLLocationDegrees)latitude
longitude:(CLLocationDegrees)longitude;
// 经纬度,latitude经度 longitude维度
@property(readonly, nonatomic) CLLocationCoordinate2D coordinate;
// 高度位置 可以正面(海拔)或负面(低于海平面)。
@property(readonly, nonatomic) CLLocationDistance altitude;
// 水平精确度,如果是负值表示不可用
@property(readonly, nonatomic) CLLocationAccuracy horizontalAccuracy;
// 垂直精确度,如果是负值表示海拔不可用
@property(readonly, nonatomic) CLLocationAccuracy verticalAccuracy;
// 真北的位置度 取值 0.0 - 359.9 度 0 表示真北
@property(readonly, nonatomic) CLLocationDirection course
// 速度 m/s 负值表示速度无效
@property(readonly, nonatomic) CLLocationSpeed speed
// 定位时间
@property(readonly, nonatomic, copy) NSDate *timestamp;
// 楼层,如果建筑物注册,可以获取楼层
@property(readonly, nonatomic, copy, nullable) CLFloor *floor
// 返回位置
@property (nonatomic, readonly, copy) NSString *description;
// 计算两个坐标的物理直线距离
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
2. CLLocationManager -- 设备方向
手机通过磁力计来判断设备方向,先看一个简单指南针的例子
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<CLLocationManagerDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)CLLocationManager *locationM;
@end
@implementation ViewController
-(CLLocationManager *)locationM
{
if (_locationM == nil) {
_locationM = [[CLLocationManager alloc]init];
_locationM.delegate = self;
}
return _locationM;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.locationM startUpdatingHeading];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
/**
* CLHeading
* magneticHeading : 距离磁北方向的角度
* trueHeading : 距离真北方向的角度
* headingAccuracy : 如果这个值是负数, 那么代表角度不可用
*/
if (newHeading.headingAccuracy < 0) {
return;
}
CGFloat angle = newHeading.magneticHeading;
CGFloat r = angle * M_PI / 180;
[UIView animateWithDuration:0.5 animations:^{
self.imageView.transform = CGAffineTransformMakeRotation(-r);
}];
}
CLLocationManager -- 关于手机朝向属性和方法
// 判断是否支持磁力计定位手机朝向
+ (BOOL)headingAvailable
// 开启手机朝向定位
- (void)startUpdatingHeading
// 关闭手机朝向定位
- (void)stopUpdatingHeading
CLLocationManagerDelegaer -- 关于手机朝向的代理方法
// 当获取一个新朝向的时候调用,持续调用
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
// manager : 管理者
// newHeading : 朝向
}
CLHeading对象基本属性
// 与磁北方向的角度 范围 0.0 - 359.9 度, 0 度表示磁北方向
@property(readonly, nonatomic) CLLocationDirection magneticHeading;
// 与真北方向的角度 范围 0.0 - 359.9 度, 0 度表示真北方向
@property(readonly, nonatomic) CLLocationDirection trueHeading;
// 返回方向值的错误范围,负值表示无效的朝向
@property(readonly, nonatomic) CLLocationDirection headingAccuracy;
// 返回方向的时间
@property(readonly, nonatomic, copy) NSDate *timestamp;
注意:当获取朝向的时候不需要向用户请求授权,因为设备方向不涉及到用户隐私
3. CLLocationManager -- 区域监听
区域监听实例
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<CLLocationManagerDelegate>
@property(nonatomic,strong)CLLocationManager *locationM;
@end
@implementation ViewController
-(CLLocationManager *)locationM
{
if (_locationM == nil) {
_locationM = [[CLLocationManager alloc]init];
_locationM.delegate = self;
[_locationM requestAlwaysAuthorization];
}
return _locationM;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 中心
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(67.666, 80.888);
// 区域半径
CLLocationDistance distance = 1000.0;
CLCircularRegion *range = [[CLCircularRegion alloc]initWithCenter:center radius:distance identifier:@"ding"];
// 用这个方法需要有位置的变化才行,从外部进来 或者出去才会有响应
// [self.locationM startMonitoringForRegion:range];
// 用这个方法就会先获取一次,判断是否在区域中
[self.locationM requestStateForRegion:range];
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(@"进入区域");
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(@"出去区域");
}
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSLog(@"%@",region);
// 获取请求指定区域状态时调用的方法
/**
CLRegionStateUnknown, // 不知道
CLRegionStateInside, // 在区域内部
CLRegionStateOutside // 在区域外部
*/
if(state == CLRegionStateInside)
{
NSLog(@"在区域中");
}else if (state == CLRegionStateOutside)
{
NSLog(@"在区域外面");
}
}
@end
注意:
[self.locationM startMonitoringForRegion:range];
开启区域监听,需要有位置的变化才会调用代理方法,例如位置从区域外部进入区域内部。
[self.locationM requestStateForRegion:range];
程序一运行就会先确定在不在区域中,当位置发生改变时也会判断在不在区域中,是进入区域还是离开区域
CLLocationManager -- 关于区域间厅属性和方法
// 判断当前设备是否支持区域监听(区域类型)
+ (BOOL)isMonitoringAvailableForClass:(Class)regionClass
// 最大的区域大小,超过这个最大值后无效
@property (readonly, nonatomic) CLLocationDistance maximumRegionMonitoringDistance
// 开启一个区域的监听
- (void)startMonitoringForRegion:(CLRegion *)region
// 请求一个区域的监听
- (void)requestStateForRegion:(CLRegion *)region
CLLocationManagerDelegaer -- 关于区域监听的代理方法
// 进入区域时调用 manager : 管理者 region:区域
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
}
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
state : 状态
CLRegionStateUnknown, // 不知道
CLRegionStateInside, // 在区域内部
CLRegionStateOutside // 在区域外部
}
CLCircularRegion对象基本属性
CLCircularRegion是CLRegion的子类
// 创建方法 cente : 中心位置 radius : 区域半径 identidier : 唯一标示
- (instancetype)initWithCenter:(CLLocationCoordinate2D)center
radius:(CLLocationDistance)radius
identifier:(NSString *)identifier;
// 中心位置
@property (readonly, nonatomic) CLLocationCoordinate2D center;
// 半径
@property (readonly, nonatomic) CLLocationDistance radius;
4. 地理编码和反地理编码
地理编码指 地址转经纬度,反地理编码指 经纬度转地址。使用CLGeocoder来获取。
CLGeocoder 的使用
// 创建
CLGeocoder *geocoder = [[CLGeocoder alloc]init];
// 地理编码
[self.geocoder geocodeAddressString:地址 completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (error == nil) {
}
}];
// 反地理编码
[self.geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (error == nil) {
}
}];
注意:方法中返回的是一个装着CLPlacemark对象的数组,是对输入地址名称或者经纬度进行检索的结果,因此返回多个结果供选择。error指错误信息,如果错误error有值
CLPlacemark基本属性
// 对应的位置对象 参考CLLocation基本属性
@property (nonatomic, readonly, copy, nullable) CLLocation *location;
@property (nonatomic, readonly, copy, nullable) CLRegion *region; // 范围
@property (nonatomic, readonly, copy, nullable) NSTimeZone *timeZone // 时区
@property (nonatomic, readonly, copy, nullable) NSString *name; // 地址名称
@property (nonatomic, readonly, copy, nullable) NSString *thoroughfare; // 街道
@property (nonatomic, readonly, copy, nullable) NSString *subThoroughfare; // 街道相关信息,例如门牌
@property (nonatomic, readonly, copy, nullable) NSString *locality; // 城市
@property (nonatomic, readonly, copy, nullable) NSString *subLocality; // 城市内分区
@property (nonatomic, readonly, copy, nullable) NSString *administrativeArea; // 直辖市
@property (nonatomic, readonly, copy, nullable) NSString *subAdministrativeArea; // 其他行政区域信息
@property (nonatomic, readonly, copy, nullable) NSString *postalCode; // 邮编
@property (nonatomic, readonly, copy, nullable) NSString *ISOcountryCode; // 国家编码
@property (nonatomic, readonly, copy, nullable) NSString *country; // 国家
@property (nonatomic, readonly, copy, nullable) NSString *inlandWater; // 水源湖泊
@property (nonatomic, readonly, copy, nullable) NSString *ocean; // 海洋
@property (nonatomic, readonly, copy, nullable) NSArray<NSString *> *areasOfInterest; // 关联的或利益相关的地标
四. iOS9/iOS8/iOS8之前的定位适配
1. iOS8.0之前是默认请求授权,需要在plist文件中加入
设置后台执行
2. iOS 8.0
使用[_locationM requestWhenInUseAuthorization];
请求获取前台定位,
[_locationM requestAlwaysAuthorization];
请求获取前后台定位,并修改plist文件。
iOS8.0以上也可以在Background Modes中设置后台定位,但是当后台定位的时候,会出现一个蓝条提醒用户正在后台定位
3. iOS 9.0
iOS 9.0 与iOS8.0一样,唯一的区别在于,当在Background Modes中设置后台定位时,需要_locationM.allowsBackgroundLocationUpdates = YES;
设置允许。
并且iOS 9.0中新添加了单次定位的方法[self.locationM requestLocation];
只获取一次位置信息。
实现逻辑:
(1) 按照定位精确度从低到高进行排序,逐个进行定位.如果在有效时间内, 定位到了精确度最好的位置, 那么就把对应的位置通过代理告知外界.
(2) 如果获取到的位置不是精确度最高的那个,也会在定位超时后,通过代理告诉外界.
注意事项:
(1) 必须实现代理的-locationManager:didFailWithError:方法
(2) 不能与startUpdatingLocation方法同时使用
五. 第三方框架LocationManager
第三方框架的使用非常简单,GitHub上已经讲解的很清晰。LocationManager是将CLLocationManager由代理向block的封装转换。CoreCLLocation使用代理,代码比较分散,第三方框架使用block来接收用户信息,并且额外增加了设置超时时间等功能,使用更简单方便易读。
文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。