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之前的版本
  • 常见问题:定位不到,对应的代理方法不执行
    1. 检查运行的模拟器是否是iOS8.0之前的系统版本
    2. 检查模拟器是否设置位置数据
    3. 确保代码无问题(一般都是代理没有设置,或者位置管理器对象是局部变量)
    4. 可能是模拟器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.监听进入/离开区域动作

  • 概念解释
  • 区域:就是指划定的一块地域范围(比如圆形区域,则由区域中心和半径组成)
  • 区域监听:通过代码指定一个区域,然后当用户持握设备进入或离开指定区域,我们都能监听到
  • 监听指定区域
    1. 导入CoreLocation框架及对应的主头文件
    2. 创建CLLocationManager对象并设置代理
    3. 请求前台定位,或前台定位授权,并在info.plist文件中配置相应的key
      • [self.locationM requestAlwaysAuthorization]
      • 或[self.locationM requestWhenInUseAutorization]
    4. 创建一个区域,并开始监听
      • 创建区域中心:CLLocationCoordinate2D center = CLLocationCorrdinate2DMake(xxx,xxx)
      • 指定区域半径:CLLocationDistance radius = 1000
      • 创建区域:CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:radius identifier:@"喳喳"];
      • 开始监听指定区域:[self.locationM startMonitoringForRegion:region];
    5. 在对应的代理方法中监听区域状态
      • 进入监听区域:locationManager:didEnterRegion
      • 离开监听区域:locationManager:didExitRegion
  • 注意事项
    • 想要做区域监听,在iOS8.0之后,必须请求位置授权
    • 原因:区域间厅的原理就是获取用户的位置,然后再判断该位置是否在指定区域内,所以会涉及到用户的隐私(位置),而在iOS8.0之后,要想访问用户位置信息,就需要主动请求授权

2.请求区域状态

  • 获取某个区域的当前状态

    • 监听某个区域时,只有进入或离开这个区域时,才能回调对应的方法,是一个进入或离开的动作
    • 如果想知道某一个区域的当前状态(识别用户是在区域内部,还是区域外部),则需要使用以下方法
      • [self.locationM requestStateForRegion:region]
    • 回调代理(请求某个区域状态时,回调的代理方法)
      • locationManager:didDetermineState:forRegion
  • 注意事项
    • 使用前,先判断区域监听是否可用

      • 代码:[CLLocationManager isMonitoringAvailabelForClass:[CLCircularRegion class]];
    • 注意区域半径是否大于最大区域监听半径(如果大于,则无法监听成功)
      • 代码:radius>self.locationM.maximumRegionMonitoringDistace
  • 常见问题
    • 区域监听,测试没有效果?

      1. 确定代码没有问题,是否有请求授权
      2. 尝试修改模拟器位置信息,触发进入区域或离开区域的动作
      3. 如果模拟器出现bug,定位不到,也会无法判定当前区域状态,所以,最后可以尝试重置模拟器

十.地理编码/反地理编码

1.功能实现

  • 概念解释

    • 地理编码:是指根据地址关键字,将其转换成对应的经纬度等信息
    • 反地理编码:是指根据经纬度信息,将其转换成为对应的省市区接到等信息
  • 地理编码
    1. 导入CoreLocation框架及对应的主头文件
    2. 创建CLGeocoder
    3. 根据地址关键字,进行地理编码
      • 直接根据地址进行地理编码,返回结果可能有多个,因为一个地点有重名
      • [self.geoC geocodeAddressString:@"北京" completionHandler:nil];
  • 反地理编码
    1. 导入CoreLocation框架及对应的主头文件
    2. 创建CLGeocoder
    3. 根据经纬度信息,进行反地理编码
      • [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类型,国家名称
  • 测试环境:
    • 必须联网/Xcode版本不限/模拟器版本不限
  • 常见问题
    • 测试无数据:检查是否联网,如果是反地理编码,可以尝试更换经纬度再次尝试,因为有的经纬度没有对应的信息

3.获取当前城市名称(定位+反地理编码)

  • 应用场景:

    • 社交app发表状态时显示位置
    • 团购app获取用户所在的区域
  • 实现步骤:
    • 定位
    • 定位后,根据位置信息,进行反地理编码

十一.使用第三方框架进行定位

  • 主要原因:

    • 因为使用CoreLocation框架进行获取用户位置信息,是通过代理进行回调.
    • 而第三方框架将"代理模式"转换成"block模式",使用起来比较方便,而且额外增加了超时时间等功能
  • 框架信息:
    • 名称:locationManager(请自行到github上搜索获取)
    • 使用方法:参照该框架对应的readme文件
  • 注意事项:
    • 一般集成第三方框架到项目中,请先确保该框架没有问题,然后再向项目中集成

十二.定位工具类的封装(代理模式到block模式的转换)

  • 主要思想就是,先记录下外界传递过来的block,然后在对应的代理方法里面执行这个block
  • 其实,实现定位还是使用代理,只不过向外界提供了一个block回调接口而已
05-11 15:12