陈斌彬的技术博客

Stay foolish,stay hungry

iOS 自带定位服务(原创)

定位服务

iOS 7 提供了4种不同的途径进行定位,具体如下所示。

  • Wi-Fi。通过 Wi-Fi 路由器的地理位置信息查询,比较省电。iPhone、iPod touch 和 iPad 都可以采用这种方式定位。
  • 蜂窝式移动电话基站。通过移动运用商基站定位。只有 iPhone、3G 版本的 iPod touch和iPad可以采用这种方式定位。
  • GPS 卫星。通过 GPS 卫星位置定位,这种方式最为准确,但是耗电量大,不能遮挡。iPhone、iPod touch 和 iPad 都可以采用这种方式定位。
  • iBeacon 微定位。iOS 7支持iBeacon技术,iBeacon 技术是苹果公司研发的,它使用低功耗蓝牙技术,通过多个 iBeacon 基站可以创建一个信号区域(地理围栏),当设备进入该区域时,相应的应用程序便会提示用户进入了这个地理围栏。

在对定位服务编程时,iOS 不像 Android 系统可以指定采用哪种途径进行定位。iOS 的 API 把底层这些细节屏蔽掉了,开发人员和用户并不知道现在设备是采用哪种方式进行定位的(微定位除外),iOS 系统会根据设备的情况 和周围的环境采用一套最佳的解决方案。

也就是说,如果能够接收 GPS 信息,那么设备优先采用 GPS 定位,否则采用Wi-Fi 或蜂窝基站定位。在 Wi-Fi 和蜂窝基站之间,优先使用 Wi-Fi,如果无法连接 Wi-Fi 才使用蜂窝基站定位。

定位服务编程

在 iOS 7 中,定位服务有比较大的变化,主要使用 Core Location 框架,定位时主要使用 CLLocationManager、 CLLocationManagerDelegate 和 CLLocation 这3个类,下面简要介绍一下它们。

  • CLLocationManager。用于定位服务管理类,它能够给我们提供位置信息和高度信息,也可以监控设备 进入或离开某个区域,还可以获得设备的运行方向等。
  • CLLocationManagerDelegate。它是 CLLocationManager 类的委托协议。
  • CLLocation。该类封装了位置和高度信息。

在定位服务的应用中,第一次请求位置信息时,系统会提示用户是否允许开启定位服务。如图所示,用户所在的位置是比较私 密的信息,应用获取这些信息时,用户是有知情权和否定权的。如果应用在用户不知情的情况下获得其位置信息,这在某些国家是违法的。 img

如果用户“不允许”,定位服务就无法获得位置信息了。如果想改变这些设置,可以在系统“设置”应用中 开启或关闭,如图所示。

如图所示,我们可以关闭所有的定位服务,此时只需关闭最上面的“定位服务”开关控件就可以了。 当然,也可以关闭或开启下面的具体应用。

img img

img

在应用启动进入界面时,会获得位置信息,并显示在对应的文本框中。如果设备位置发生变化,也会重新获取位置信息,并更新对应的文本框。

img

首先,为工程引入 Core Location 框架,具体步骤是选择工程中的 TARGETS→WhereAmI→Build Phases→Link Binary With Libraries,选择右下角的 + 按钮,打开“选择要添加的框架和库”对话框,如图所示。

img

ViewController.h 中的代码如下:

img

在上述代码中,我们首先引入了 CoreLocation/CoreLocation.hCoreLocation/CLLocationManagerDelegate.h 这两个头文件,然后在定义ViewController 时声明了 CLLocationManagerDelegate 协议。此外,我们还定义了 CLLocationManager *locationManager 属性。 在 ViewController.m 中,viewDidLoad 方法的代码如下:

- (void)viewDidLoad
{
[super viewDidLoad];
//初始化定位服务管理对象
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest; (1)
_locationManager.distanceFilter = 1000.0f; (2)
}

img

在上述代码中,我们主要对 CLLocationManager 的成员变量 _locationManager 进行了初始化。首先,使用 [[CLLocationManager alloc] init] 语句实例化 CLLocationManager 对象,然后使用 _locationManager. delegate = self 语句设置定位服务委托为 self。

第(1)行代码设置 desiredAccuracy 属性,它是一个非常重要 的属性,其取值有6个常量,具体如下所示。

  • kCLLocationAccuracyNearestTenMeters。精确到10米。
  • kCLLocationAccuracyHundredMeters。精确到100米。
  • kCLLocationAccuracyKilometer。精确到1000米。
  • kCLLocationAccuracyThreeKilometers。精确到3000米。
  • kCLLocationAccuracyBest。设备使用电池供电时最高的精度。
  • kCLLocationAccuracyBestForNavigation。导航情况下最高的精度,一般有外接电源时才能使用。

精度越高,请求获得位置信息的时间就越短,这就意味着设备越耗电,因此一个应用应该选择适合它的精度。 如果你的应用是一个车载导航应用, kCLLocationAccuracyBestForNavigation 是比较好的选择,你可以使用汽车上的电瓶为设备供电。 如果你的应用是为徒步旅行者提供的导航应用, kCLLocationAccuracyHundredMeters 是一个不错的选择。

第(2)行代码设置 distanceFilter 属性,它是距离过滤器,定义了设备移动后获得位置信息的最小距离,单位是米,本例设置为1000米。 初始化 CLLocationManager 类后,需要使用 startUpdatingLocation 方法开始定位服务,该方法定义在 ViewController.mviewWillAppear: 方法中。viewWillAppear:方法的代码如下:

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated]; //开始定位
[_locationManager startUpdatingLocation];
}

调用 startUpdatingLocation 方法时,就会开启定位服务。根据设定的条件,它不断请求回调新的位置信息。因此,开启这个方法一定要慎重,在视图控制器的声明周期方法 viewWillAppear: 中使用这个方法是最合适的。与开启服务对应的方法是 stopUpdatingLocation 方法,它是在视图控制器的 viewWillDisappear: 方 法中调用的。viewWillDisappear: 方法的代码如下:

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated]; //停止定位
[_locationManager stopUpdatingLocation];
}

img

这个方法在视图消失(应用退到后台)时调用,能够保证最及时地关闭定位服务。在iOS 6中,请求有所变化, 定位服务应用退入台后,可以延迟更新位置信息,这可以通过 allowDeferredLocationUpdatesUntil- Traveled:timeout: 方法实现。要关闭延迟更新,可以使用 disallowDeferredLocationUpdates 方法实现。 此外,在iOS 6中,新增了 pausesLocationUpdatesAutomatically 属性,它能设定自动暂停位置更新,而把 定位服务的开启和暂停管理权交给系统,这样会更加合理和简单。

一旦定位服务开启,并设置好 CLLocationManager 委托属性 delegate 后,当用户设备移动到达过滤距离时, 就会回调委托方法。与定位服务有关的方法有如下两个。

  • locationManager:didUpdateLocations:。定位成功。这是iOS 6中新增的方法,替代了之前的 locationManager:didUpdateToLocation:fromLocation: 方法。
  • locationManager:didFailWithError:。定位失败。

实现 CLLocationManager 委托的代码如下:

#pragma mark Core Location委托方法用于实现位置的更新
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:
        (NSArray *)locations
{
CLLocation * currLocation = [locations lastObject]; (1)
_txtLat.text = [NSString stringWithFormat:@"%3.5f",currLocation.coordinate.latitude]; (2)
_txtLng.text = [NSString stringWithFormat:@"%3.5f",currLocation.coordinate.longitude]; (3)
_txtAlt.text = [NSString stringWithFormat:@"%3.5f",currLocation.altitude]; (4)
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
    {
        NSLog(@"error: %@",error);
}

img

locationManager:didUpdateLocations: 方法中,参数 locations 是位置变化的集合,它按照时间变化的顺序存放。如果想获得当前设备的位置,可以使用 第(1)行中的 [locations lastObject] 语句获得集合中的最后一个元素,它就是设备的当前位置了。从集合中返回的对象类型是 CLLocation,CLLocation 封装了位置、高度等信息。在上面的代码中,我们使用了它的两个属性 altitudecoordinate ,其中前者是高度值,后者是封装经度和纬度的结构体CLLocationCoordinate2DCLLocationCoordinate2D 的定义如下:

typedef struct {
CLLocationDegrees latitude; //纬度 CLLocationDegrees longitude; //经度
} CLLocationCoordinate2D; 

其中 latitude 为纬度信息,longitude 为经度信息,它们都是 CLLocationDegrees 类型。CLLocationDegrees 是使用 typedef 定义的 double 类型。

第(2)行代码中的 currLocation.coordinate.latitude 表达式用于获得设备当前的纬度,

第(3)行代码中的 currLocation.coordinate.longitude 表达式用于获得设备当前的经度,

第(4)行代码中的 currLocation. altitude 表达式用于获得高度。

关于定位服务的测试

一般情况下,定位服务应用的测试和运行有两个选择:模拟器和设备。原则上,我们先通过模拟器,然后再 使用设备测试。由于定位服务的特点,使用设备测试时我们需要到现场进行测试,所以有的时候有一定的局限性。 因此,使用模拟器测试有的时候是不可替代的。

在 Xcode 早期版本中,模拟器是不能模拟位置信息的变化的,请求获取位置信息只是固定苹果公司总部地址。 而现在的 Xcode 版本预先设置了几个地址,我们可以模拟改变位置。如果想让模拟器一开始运行的时候就能够获 得模拟数据,可以在启动参数中设置。首先,在 Xcode 工具的左上角编辑应用的 Scheme,如图所示。

img

选择 Edit Scheme 菜单后,弹出如图所示的对话框,从中选择 Run WhereAmI.app→Options,在 Core Location项目中选中Allow Location Simulation复选框,然后在下面的 Default Location 下拉框中选择你感兴趣的城市。

img

这样应用启动时,就会模拟定位到你选择的城市了。如果列表中没有我们需要的地点,可以使用最下面的 Add GPX File to Project 菜单项为工程添加一个 GPX1 文件。下面是 GPX 文件的内容:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1"
    creator="MyGeoPosition.com" version="1.1"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.topografix.com/GPX/1/1
        http://www.topografix.com/GPX/1/1/gpx.xsd">
    <wpt lat="40.002240" lon="116.323328"> 
<name>中国北京 东城区北京站东街北京 邮政编码: 100005</name> <src>MyGeoPosition.com</src> <link>http://mygeoposition.com</link>
        </wpt>
    </gpx>

<wpt> 标签中的 lat属性设置纬度,lon 属性设置经度。自己手写这个文件还是比较麻烦的,一般使用 http://www.mygeoposition.com 网站提供的 GPX工具工具生成。这个网站免费提供地理信息编码和反编码、生成 KML 和 GPX 文件等服务。

GPX(GPS eXchange Format,GPS交换格式)是一个 XML 格式,是为应用软件设计的通用 GPS 数据格式。

得到 GPX 文件后,可以通过如图所示的 Add GPX File to Project 菜单项将它添加到 Xcode 工程中,此时在菜单中就会出现 GPX 文件了。如果我添加的文件名是 test.gpx ,则在菜单中出现 test 菜单项,选择 test 即可使用这个模拟坐标数据了。

img

如果在应用启动参数中没有设置初始的模拟位置数据,我们还可以在运行之后设置。在调试工具栏中选择模 拟定位按钮,即可选择模拟位置,如图所示。

img

Xcode 中的模拟器还提供了连续位置变化测试能力。如果想开发导航应用,这个功能对我们有很大的帮助。 此外,模拟器有几个固定的模式,可以发出连续变化的位置数据。打开模拟菜单的“调试”→“位置”,可以发 现共有7个菜单项,

img

其中后面3个都能发出连续的位置变化数据,它们的起始点从苹果公司总部开始,按照一个固定的线路运动,这三者的区别是 City Bicycle Ride 是最慢的,City Run 要快一些,Freeway Drive 最快。