iOS-世界那么大,CoreLocation带你去看看

一. 简介

在我们日常生活中时常用到地图和定位功能,来导航去你想去的地方或者寻找周边的景点,餐厅,电影院等等,在iOS开发中,要想加入这两大功能,必须基于两个框架进行开发,有了这两个框架,想去哪就去哪。
CoreLocation :用于地理定位,地理编码,区域监听等(着重功能实现)
MapKit :用于地图展示,例如大头针,路线、覆盖层展示等(着重界面展示)

二. CoreLocation框架的基本使用

1. CoreLocation使用步骤

  1. 导入CoreLocation框架。
  2. 创建CLLocationManager管理者对象。
  3. 遵循代理,并实现代理方法。
  4. 设置获取用户前后台定位授权
  5. 开始定位。

三. 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中加入的信息表示获取定位授权时显示的信息。

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文件中加入

iOS 8.0之前

设置后台执行


设置后台执行

2. iOS 8.0

使用[_locationM requestWhenInUseAuthorization]; 请求获取前台定位,
[_locationM requestAlwaysAuthorization];请求获取前后台定位,并修改plist文件。

iOS8.0+请求授权

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,一只长大很久但还没有二够的家伙。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容