CoreLocation框架使用
一.地图和定位的简介
1.应用场景
- 周边:找餐馆/找KTV/找电影院(团购APP)
- 导航:根据用户设定的起点和终点,进行路线规划,并指引用户如何到达(地图APP)
2.iOS中加入定位和地图功能所依赖的框架
- CoreLocation
- 地理定位:定位用户所在的位置,获取对应的经纬度或者海拔等信息
- 地理编码:具体位置->经纬度坐标
- 反地理编码:经纬度坐标->具体位置
- 区域监听:事先在APP内部通过代码,指定一个区域,当用户进入或离开区域的时候,都可以监听到
- MapKit
- 地图展示:展示一个地图给用户看,而且也可以在地图上添加一些大头针/路线/覆盖层等
- 路线规划:给定两个位置信息,可以获取到两个点之间的导航数据(行走路线/行走步骤/行走时间等)
3.两个热门专业术语
- LBS(Location Based Service):基于位置的服务
- SoLoMo(Social Local Mobile):索罗门
- 社交化:在APP里面假如一些社交元素
- 本地化:基于LBS的周边搜索,周边签到等服务
- 移动化:移动网(3G/4G网,相对于有线/无线电脑网络),移动APP(相对于桌面应用)
4.使用CoreLocation框架进行定位
- 导入框架(Xcode5.0之后可以省略)
- 导入主头文件 #import \<CoreLocation/CoreLocation.h>
- 使用CLLocationManager对象来做很多相关的位置服务
二.iOS8.0之前的定位(了解)
1.为什么了解
- iOS8之前的版本慢慢即将被淘汰,不再做适配
- 只要iOS8以后的定位功能实现,那么直接把代码跑到iOS8之前的设备上,依然是可以运行得,不需要做任何修改
- 因为系统版本和Xcode版本原因,暂时没法安装iOS8之前的模拟器进行安装
2.iOS8.0之前的前台定位
- 导入CoreLocation框架及对应的主头文件
- #import \<CoreLocation/CoreLocation.h>
- 创建CLLocationManager对象并设置代理
- self.locationM = [[CLLocationManager alloc] init];
- self.lacationM.delegate = self;
- 调用方法,开始更新用户位置信息
- [self.locationM startUpdatingLocation];
- 在对应的代理方法中获取位置信息
- locationManager:didUpdataLocations
- 可以在info.plist文件中,配置Privacy - Location Usage Description来说明定位目的
3.iOS8.0之前的后台定位
- 后台定位:
- 在前台定位的基础上
- 勾选后台模式(Background Modes) - location updates
- 或者直接设置info.plist文件,Required background modes -> App register for location updates
- 测试环境:
- Xcode7.0之前版本,例如Xcode6.4版本
- 模拟器选择iOS8.0之前的版本
- 常见问题:定位不到,对应的代理方法不执行
- 检查运行的模拟器是否是iOS8.0之前的系统版本
- 检查模拟器是否设置位置数据
- 确保代码无问题(一般都是代理没有设置,或者位置管理器对象是局部变量)
- 可能是模拟器bug,将模拟器位置设为none,然后再次设置数据,或者,重置模拟器
三.iOS8.0之后的定位
1.iOS8.0之后的前台定位
- 前台定位
- 在iOS8.0之前定位的代码基础上
- 主动请求前台定位授权,并在info.plist文件中配置对应的key
- [self.locationM requestWhenInUseAuthorization];
- KEY: NSLocationWhenInUseUsageDescription
- 注意
- 不要忘记做版本适配
- 不要忘记在info.plist中配置对应的key,千万注意key不要出现空格,很难检查
- 测试环境
- Xcode版本无要求
- 模拟器选择iOS8.0之后(包含iOS8.0)
2.iOS8.0之后的后台定位-方案1
- 在前台定位的基础上,勾选后台模式location updates
- 效果:当APP退到后台,会出现一个蓝条,不断提醒用户
3.iOS8.0之后的后台定位-方案2
- 在定位时,直接请求前后台定位授权,并在info.plist中配置对应的key
- [self.locationM requestAlwaysAuthorization];
- KEY:NSLocationAlwaysUsageDescription
- 效果:无论是否勾选后台模式,都可以获取位置信息,而且无论前后台,都不会出现蓝条
- 注意:
- 不要忘记做版本适配
- 不要忘记在info.plist中配置对应的key,千万注意key不要出现空格,很难检查
- 测试环境
- Xcode版本无要求
- 模拟器选择iOS8.0之后,iOS9.0之前的版本
四.iOS9.0之后的定位
1.iOS9.0之后的定位
- 定位变化
- 前台定位:和iOS8.0之后一致,没有变化
- 后台定位方案一:
- 在前台定位授权的基础上,如果勾选了后台模式location updates之后,还需要额外设置属性allowBackgroundLocationUpdates = YES;
- 后台定位方案二:
- 直接请求前后台定位授权的情况,和iOS8.0之后一致,没有变化
- 测试环境
- Xcode7.0之后的版本
- 模拟器选择iOS9.0之后的版本
五.定位的补充
1.监听用户授权状态
- 实现位置管理者CLLocationManager代理方法
- locationManager:didChangeAuthorizationStatus:
- 各个授权状态对应的含义
- kCLAuthorizationStatusNotDetermined:用户未决定
- kCLAuthorizationStatusRestricted:定位服务访问受限(系统预留字段)
- kCLAuthorizationStatusDenied:定位被拒绝
- kCLAuthorizationStatusAuthorizedAlways:前后台定位授权
- kCLAuthorizationStatusAuthorizedWhenInUse:前台定位授权
- 开发经验
- 如果进入到被拒绝的分支,应该判断是关闭了定位服务,还是真正被拒绝,需要根据不同的情况,给用户提示做出不同的选择
- 直接跳转到"授权界面"的方法
- URL:prefs:root=LOCATION_SERVICES
- UIApplicationOpenSettingURLString(iOS8.0之后)
2.额外参数设置
- 额外设置
- 每隔多少米定位一次:distanceFilter
- 设置定位精确度:desiredAccuracy
- kCLLocationAccuracyBestForNavigation:最适合导航
- kCLLocationAccuracyBest:精度最好的
- kCLLocationAccuracyNearestTenMeters:附近10米
- kCLLocationAccuracyHundredMeters:附近100米
- kCLLocationAccuracyKilometer:附近1000米
- kCLLocationAccuracyThreeKilometers:附近3000米
- 开发经验
- 定位本身就非常耗电,定位的精确度越高,越耗电,定位时间越长
- 为了省电,尽量在满足需求的情况下降低精确度
3.知识补充
- 标准定位服务
- 定位实现方案:基于GPS/蓝牙/基站/WiFi定位,具体使用哪种,苹果有自己的规则
- 优点:定位精度高
- 缺点:程序关闭,就没法获取位置,而且耗电
- 显著位置变化定位服务
- 定位实现方案:基于基站定位,必须要求设备有电话模块
- 优点:当app被完全关闭时,也可以接收到位置通知,并让app进入到后台处理
- 缺点:定位精度低
- 应用场景:
- 如果要求定位及时,精度较高,并且运行时间较短,可使用标准定位
- 如果长时间监控用户位置,用户移动速度比较快(比如,打车软件),可使用后者
- 新的API
- 单次定位请求
- 代码:[self.locationM requestLocation];
- 功能:获取一次位置信息
- 实现逻辑:
- 按照定位精确度从低到高进行排序,逐个进行定位.如果在有效时间内,定位到了精确度最好的位置,那么就把对应的位置通过代理告知外界.
- 如果获取到的位置不是精确度最高的那个,也会在定位超时后,通过代理告诉外界
- 注意事项:
- 必须实现代理的locationManager:didFailWithError:方法
- 不能与startUpdatingLocation方法同时使用
- 常见问题:
- 单次定位在模拟器上测试不出效果?
- 答:因为模拟器的位置是固定的,所以无法测试出下效果,需要使用真机进行测试.
- 单次定位,控制台打印多次位置信息
- 答:因为模拟器bug,需要以真机测试为准,亲测,只打印一次.
六.CLLocation对象
1.CLLocation对象详解
- 属性解释
- coordinate:当前位置所在的经纬度数据
- altitude:海拔
- speed:当前速度
- course:航向(设备的移动方向,值域范围0.0~359.9,正北方向为0.0)
- 重要方法
- distanceFromLocation:location
- 作用:计算两个位置对象之间的物理距离,单位是米
- 开发经验
- 使用之前,务必判断数据是否有效
- 代码:if(location.horizontalAccuracy<0) return;
- 功能:如果水平精度小于0,带包虽然可以获取位置对象,但是数据错误,不可用
2.CLLocation场景演练
七.定位的经验小结
1.定位的应用场景
- 导航
- 电商APP,获取用户所在的城市(需要与地理编码/反地理编码联合使用)
- 数据采集用户信息(例如,统计app使用分布)
- 查找周边(周边好友,周边商家等等)
2.开发经验
- 由于定位非常耗电,所以为了给用户省电,有如下经验:
- 不需要获取用户位置时,一定要关闭定位服务
- 如果可以,尽可能使用低精度的desiredAccuracy
- 如果是数据采集(一般都是周期性的去轮询用户位置),在轮询期一定要关闭定位
八.指南针效果的实现
1.实现思路
- 利用"磁力计"传感器,获取设备朝向
- 根据设备朝向,反向旋转"指南针"图片
2.代码实现
- 获取设备朝向
- 导入CoreLocation框架及对应的主头文件
- 创建CLLocationManager对象并设置代理
- 调用[self.locationM startUpdatingHeading]方法,开始获取设备朝向
- 在对应的代理方法中获取设备朝向信息
- locationManager:didUpdateHeading
- 旋转图片
- 判断当前的角度(newHeading.headingAccuracy)是否有效(如果此值小于0,代表角度无效)
- 获取当前设备朝向(磁北方向)
- CGFloat angle = newHeading.magneticHeading;
- 转换成弧度
- CGFloat radian = angle / 180.0 * M_PI;
- 带动画反向旋转指南针
- self.compassView.transform = CGAffineTransformMakeRotation(-radian);
3.概念补充
- 磁北角度(newHeading.magneticHeading):相对于"磁北方向"产生的角度
- 真北角度(newHeading.trueHeading):相对于"真北方向"产生的角度
4.注意事项
- 获取设备朝向前,先判断"磁力计"是否可用
- [CLLocationManager headingAvailable];
- 获取朝向信息前,判断当前朝向信息是否有效
- 判断newHeading.headingAccuracy是否小于0,如果小于0,则无效
- 注意"设备朝向"和"航向"的区别:
- 设备朝向:是指手机的朝向
- 航向:可以理解为设备的移动方向
- 使用"磁力计"传感器获取设备朝向,不需要请求用户授权,因为设备朝向不涉及用户隐私
5.测试环境
- Xcode版本无要求(建议7.0+,因为不需要开发者账号也可以进行真机调试)
- 必须要求真机设备(只有真机设备才有"磁力计"传感器)
九.区域监听
1.监听进入/离开区域动作
- 概念解释
- 区域:就是指划定的一块地域范围(比如圆形区域,则由区域中心和半径组成)
- 区域监听:通过代码指定一个区域,然后当用户持握设备进入或离开指定区域,我们都能监听到
- 监听指定区域
- 导入CoreLocation框架及对应的主头文件
- 创建CLLocationManager对象并设置代理
- 请求前台定位,或前台定位授权,并在info.plist文件中配置相应的key
- [self.locationM requestAlwaysAuthorization]
- 或[self.locationM requestWhenInUseAutorization]
- 创建一个区域,并开始监听
- 创建区域中心:CLLocationCoordinate2D center = CLLocationCorrdinate2DMake(xxx,xxx)
- 指定区域半径:CLLocationDistance radius = 1000
- 创建区域:CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:radius identifier:@"喳喳"];
- 开始监听指定区域:[self.locationM startMonitoringForRegion:region];
- 在对应的代理方法中监听区域状态
- 进入监听区域:locationManager:didEnterRegion
- 离开监听区域:locationManager:didExitRegion
- 注意事项
- 想要做区域监听,在iOS8.0之后,必须请求位置授权
- 原因:区域间厅的原理就是获取用户的位置,然后再判断该位置是否在指定区域内,所以会涉及到用户的隐私(位置),而在iOS8.0之后,要想访问用户位置信息,就需要主动请求授权
2.请求区域状态
- 获取某个区域的当前状态
- 监听某个区域时,只有进入或离开这个区域时,才能回调对应的方法,是一个进入或离开的动作
- 如果想知道某一个区域的当前状态(识别用户是在区域内部,还是区域外部),则需要使用以下方法
- [self.locationM requestStateForRegion:region]
- 回调代理(请求某个区域状态时,回调的代理方法)
- locationManager:didDetermineState:forRegion
- 注意事项
- 使用前,先判断区域监听是否可用
- 代码:[CLLocationManager isMonitoringAvailabelForClass:[CLCircularRegion class]];
- 注意区域半径是否大于最大区域监听半径(如果大于,则无法监听成功)
- 代码:radius>self.locationM.maximumRegionMonitoringDistace
- 常见问题
- 区域监听,测试没有效果?
- 确定代码没有问题,是否有请求授权
- 尝试修改模拟器位置信息,触发进入区域或离开区域的动作
- 如果模拟器出现bug,定位不到,也会无法判定当前区域状态,所以,最后可以尝试重置模拟器
十.地理编码/反地理编码
1.功能实现
- 概念解释
- 地理编码:是指根据地址关键字,将其转换成对应的经纬度等信息
- 反地理编码:是指根据经纬度信息,将其转换成为对应的省市区接到等信息
- 地理编码
- 导入CoreLocation框架及对应的主头文件
- 创建CLGeocoder
- 根据地址关键字,进行地理编码
- 直接根据地址进行地理编码,返回结果可能有多个,因为一个地点有重名
- [self.geoC geocodeAddressString:@"北京" completionHandler:nil];
- 反地理编码
- 导入CoreLocation框架及对应的主头文件
- 创建CLGeocoder
- 根据经纬度信息,进行反地理编码
- [self.geoC reverseGeocodeLocation:[[CLLocation alloc] initWithLatitude:xxx longtitude:xxx] completionHandler:nil];
2.CLPlacemark对象详解
- CLPlacemark地标对象详解
- location:CLLocation类型,位置对应信息,里面包含经纬度,海拔等信息
- region:CLRegion类型,地标对象对应的区域
- addressDictionary:NSDictionary类型,存放街道,省市等信息
- name:NSString类型,地址全称
- thoroughfare:NSString类型,街道名称
- locality:NSString类型,城市名称
- administrativeArea:NSString类型,省名称
- country:NSString类型,国家名称
- 测试环境:
- 常见问题
- 测试无数据:检查是否联网,如果是反地理编码,可以尝试更换经纬度再次尝试,因为有的经纬度没有对应的信息
3.获取当前城市名称(定位+反地理编码)
- 应用场景:
- 社交app发表状态时显示位置
- 团购app获取用户所在的区域
- 实现步骤:
十一.使用第三方框架进行定位
- 主要原因:
- 因为使用CoreLocation框架进行获取用户位置信息,是通过代理进行回调.
- 而第三方框架将"代理模式"转换成"block模式",使用起来比较方便,而且额外增加了超时时间等功能
- 框架信息:
- 名称:locationManager(请自行到github上搜索获取)
- 使用方法:参照该框架对应的readme文件
- 注意事项:
- 一般集成第三方框架到项目中,请先确保该框架没有问题,然后再向项目中集成
十二.定位工具类的封装(代理模式到block模式的转换)
- 主要思想就是,先记录下外界传递过来的block,然后在对应的代理方法里面执行这个block
- 其实,实现定位还是使用代理,只不过向外界提供了一个block回调接口而已