9.2 获取定位信息
iOS开发者使用CoreLocation.framework框架进行定位非常简单,CoreLocation框架的常用API主要有如下几个。
Ø CLLocationManager:定位管理器类。
Ø CLLocationManagerdelegate:该协议代表定位管理器的delegate协议。实现该协议的对象可负责处理CLLocationManager的定位事件。
Ø CLLocation:该对象代表位置。该对象包含了当前设备的经度、纬度、高度、速度、路线等信息,还包含了该定位信息的水平精确度、垂直精确度以及时间戳信息。
Ø CLHeading:该对象代表设备的移动方向。
Ø CLRegion:该对象代表一个区域。一般程序不会直接使用该类,而是使用它的两个子类,即CLCircularRegion(圆形区域)和CLBeaconRegion(蓝牙信号区)。
除此之外,CoreLocation框架还涉及一个CLLocationCoordinate2D结构体变量,该结构体变量包含经度、纬度两个值。其中CLLocation对象的coordinate属性就是一个CLLocationCoordinate2D结构体变量。
了解CoreLocation提供的这些API之后,接下来即可通过这些API进行定位了。
9.2.1 获取位置信息
使用CoreLocation.framework进行定位只要如下3步即可。
创建CLLocationManager对象,该对象负责获取定位相关信息。并为该对象设置一些必要的属性。
为CLLocationManager指定delegate属性,该属性值必须是一个实现CLLocationManagerDelegate协议的对象。实现CLLocationManagerDelegate协议时可根据需要实现协议中特定的方法。
调用CLLocationManager的startUpdatingLocation方法获取定位信息。定位结束时,可调用stopUpdatingLocation方法结束获取定位信息。
注意:
为了在iOS应用中使用CoreLocation.framework,需要完成两件事情:①为应用添加CoreLocation.framework框架;②在需要使用定位服务及相关类的源文件中使用“#import <CoreLocation/CoreLocation.h>”导入CoreLocation.framework的头文件。本章绝大部分示例都使用了CoreLocation.framework,因此都需要执行上面两步操作。
从上面介绍不难看出,使用CoreLocation进行定位的关键就是CLLocationManager对象及其delegate对象。其中CLLocationManager负责获取定位信息,而delegate则负责处理定位事件——通过这些事件即可获取设备所在位置。
CLLocationManager还提供了如下类方法来判断当前设备的定位相关服务状态。
Ø + locationServicesEnabled:返回当前定位服务是否可用。
Ø + deferredLocationUpdatesAvailable:返回延迟定位更新是否可用。
Ø + significantLocationChangeMonitoringAvailable:返回重大位置改变监听是否可用。
Ø + headingAvailable:返回该设备是否支持磁力计计算方向。
Ø + isRangingAvailable:返回蓝牙信号范围服务是否可用。这是iOS 7新增的方法。
除此之外,在使用CLLocationManager开始定位之前,还可为该对象设置如下属性。
Ø pausesLocationUpdatesAutomatically:设置iOS设备是否可暂停定位来节省电池的电量。如果该属性设为“YES”,则当iOS设备不再需要定位数据时,iOS设备可以自动暂停定位。
Ø distanceFilter:设置CLLocationManager的自动过滤距离。也就是说,只有当设备在水平方向的位置改变超过该数值(以米为单位)指定的距离时才会生成一次位置改变的信号。
Ø desiredAccuracy:设置定位服务的精度。该属性值支持kCLLocationAccuracyBestForNavigation(导航级的最佳精确度)、kCLLocationAccuracyBest(最佳精确度)、kCLLocationAccuracy NearestTenMeters(10米误差)、kCLLocationAccuracyHundredMeters(百米误差)、kCLLocationAccuracyKilometer(千米误差)、kCLLocationAccuracyThreeKilometers(三千米误差)等常量值。当然,也可直接指定一个浮点数作为定位服务允许的误差。
Ø activityType:设置定位数据的用途。该属性支持CLActivityTypeOther(定位数据作为普通用途)、CLActivityTypeAutomotiveNavigation(定位数据作为车辆导航使用)、CLActivityTypeFitness(定位数据作为步行导航使用)和CLActivityTypeOtherNavigation(定位数据作为其他导航使用)这几个枚举值之一。
接下来通过示例来示范使用CoreLocation定位iOS设备的位置。
创建一个Single View Application,该项目包含一个应用程序委托类、一个视图控制器类和Main.storyboard界面设计文件。打开该项目的界面设计文件,向其中添加5个文本框,分别用于显示当前设备的经度、纬度、高度、速度和方向,并在界面上添加一个UIButton按钮。
为了在程序中访问界面上的5个文本框,需要将它们分别绑定到视图控制器类的longitudeTxt、latitudeTxt、altitudeTxt、speedTxt、courseTxt这5个IBOutlet属性;为了让程序能响应按钮的点击事件,还需要为按钮的“Touch Up Inside”事件绑定bnTapped:事件处理方法。
下面是该视图控制器类的实现部分代码。
程序清单:codes/09/9.2/LocationTest/LocationTest/FKViewController.m
@interface FKViewController () <CLLocationManagerDelegate> @property (strong,nonatomic)CLLocationManager *locationManager; @end @implementation FKViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建CLLocationManager对象 self.locationManager = [[CLLocationManager alloc]init]; } - (IBAction)bnTapped:(id)sender { // 如果定位服务可用 if([CLLocationManager locationServicesEnabled]) { NSLog( @"开始执行定位服务" ); // 设置定位精度:最佳精度 self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // 设置距离过滤器为50米,表示每移动50米更新一次位置 self.locationManager.distanceFilter = 50; // 将视图控制器自身设置为CLLocationManager的delegate // 因此该视图控制器需要实现CLLocationManagerDelegate协议 self.locationManager.delegate = self; // 开始监听定位信息 [self.locationManager startUpdatingLocation]; } else { NSLog( @"无法使用定位服务!" ); } } // 成功获取定位数据后将会激发该方法 -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { // 获取最后一个定位数据 CLLocation* location = [locations lastObject]; // 依次获取CLLocation中封装的经度、纬度、高度、速度、方向等信息 self.latitudeTxt.text = [NSString stringWithFormat:@"%g", location.coordinate.latitude]; self.longitudeTxt.text = [NSString stringWithFormat:@"%g", location.coordinate.longitude]; self.altitudeTxt.text = [NSString stringWithFormat:@"%g", location.altitude]; self.speedTxt.text = [NSString stringWithFormat:@"%g", location.speed]; self.courseTxt.text = [NSString stringWithFormat:@"%g", location.course]; } // 定位失败时激发的方法 - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { NSLog(@"定位失败: %@",error); } @end
上面程序中的第1段粗体字代码为CLLocationManager对象设置了属性之后,关键是将该视图控制器设置为CLLocationManager的delegate,程序调用CLLocationManager的startUpdatingLocation方法开始获取定位数据。
由于程序指定该视图控制器作为CLLocationManager的delegate,因此该视图控制器需要实现CLLocationManagerDelegate协议,并实现该协议中定位相关的两个事件处理方法。当程序成功获取定位数据时,将会激发delegate的locationManager:didUpdateLocations:方法,因此上面视图控制器类实现了该方法,并在该方法中获取最后一个定位数据:CLLocation对象。
CLLocation对象中包含如下属性,这些属性包含了定位相关信息。
Ø altitude:该属性表示当前设备的海拔高度,单位是米。
Ø coordinate:该属性返回一个CLLocationCoordinate2D结构体变量,该结构体变量中包含经度、纬度信息。
Ø course:该属性表示当前设备前进的方向。该值为0°表示向北,90°表示向东,180°表示向南,270°表示向西。
Ø horizontalAccuracy:该属性表明定位信息的水平精确度。将返回的坐标作为圆心,并将水平精确度视为半径。真正的设备位置落在此圆内的某处。此圆越小,位置就越精确;此圆越大,则位置越不精确。如果精确度为负值,则表明测量精确度失败。
Ø verticalAccuracy:该属性表明定位信息的垂直精确度。也就是说,iOS设备的实际高度在该定位信息的高度加或减该属性值的范围内。
Ø timestamp:该属性返回定位信息的返回时间。
Ø speed:该属性表示返回设备的移动速度,单位是米/秒。实际上,该属性适用于行车速度,而不太适用于步行速度。
每个 iOS 应用第一次使用定位功能时都会因为权限问题,而弹出是否允许当前应用程序获取定位操作权限的提示框,如图 9.1 所示。
图9.1 询问用户是否允许该应用使用定位功能
单击“OK”按钮,即可在Xcode控制器中看到“开始执行定位服务”的提示信息,但可能依然看不到程序界面上有任何输出——此时我们可以通过模拟器来模拟设备的位置。
9.2.2 使用iOS模拟器模拟位置
iOS模拟器本身并不能作为GPS接收机,因此无法得到定位信息,但为了方便程序员测试定位应用,iOS模拟器可以模拟定位信息。
启动iOS模拟器之后,即可通过iOS模拟器主菜单中的“调试”→“位置”来模拟iOS设备的位置,该菜单如图9.2所示。
图9.2所示菜单支持如下几种位置信息。
Ø 自定位置:开发者可以自行输入位置的经度值、纬度值。
Ø City Bicycle Ride:模拟设备携带者在城市中骑车移动。
Ø City Run:模拟设备携带者在城市中跑动。
Ø Freeway Drive:模拟设备携带者在高速公路中驾车。
如果选择“Freeway Driver”来模拟该设备携带者在高速公路中驾车,则可以看到该应用显示如图9.3所示。
9.2.3 监控行车速度和行车距离
上一个示例是通过CLLocation对象来获取设备的移动速度和移动方向,但这种移动速度属性适用于行车速度,而不太适用于步行速度。如果希望程序计算平均移动速度,则只要不断地累计设备的移动距离和移动时间,再用距离除以时间即可得到设备的平均移动速度。
下面通过一个示例来计算设备的平均移动速度。新建一个Single View Application,打开该应用的Main.storyboard界面设计文件,向该界面设计文件中拖入一个UITextView控件,用于显示该设备的移动速度。为了在程序中访问该UITextView控件,程序将它绑定到视图控制器的showView控件。
接下来修改视图控制器类,在视图控制器类中通过设备的移动距离和移动时间来计算速度。下面是该视图控制器类的实现部分代码。
程序清单:codes/09/9.2/SpeedMonitor/SpeedMonitor/FKViewController.m
#import "FKViewController.h" #import <CoreLocation/CoreLocation.h> @interface FKViewController () <CLLocationManagerDelegate> @property (nonatomic , retain) CLLocationManager *locationManager; @property (nonatomic , retain) CLLocation *prevLocation; @property (nonatomic , assign) CGFloat sumDistance; @property (nonatomic , assign) CGFloat sumTime; @end @implementation FKViewController - (void) viewDidLoad { [super viewDidLoad]; // 创建CLLocationManager对象 self.locationManager = [[CLLocationManager alloc] init]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // 设置定位精度:最佳精度 self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // 设置距离过滤器为50米,表示每移动50米更新一次位置 self.locationManager.distanceFilter = 50; // 将视图控制器自身设置为CLLocationManager的delegate // 因此该视图控制器需要实现CLLocationManagerDelegate协议 self.locationManager.delegate = self; // 开始监听定位信息 [self.locationManager startUpdatingLocation]; NSLog(@"开始执行定位服务" ); } // 定位失败时激发的方法 - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { NSLog(@"定位失败: %@",error); } // 成功获取定位数据后将会激发该方法 -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { // 获取最后一个定位数据 CLLocation* newLocation = [locations lastObject]; if(newLocation.horizontalAccuracy < kCLLocationAccuracyHundredMeters) { if(self.prevLocation) { // 计算本次定位数据与上次定位数据之间的时间差 NSTimeInterval dTime = [newLocation.timestamp timeIntervalSinceDate:self.prevLocation.timestamp]; // 累计行车时间 self.sumTime += dTime; // 计算本次定位数据与上次定位数据之间的距离 CGFloat distance = [newLocation distanceFromLocation:self.prevLocation]; // 如果距离小于1米,则忽略本次数据,直接返回该方法 if(distance < 1.0f) { return; } // 累加移动距离 self.sumDistance += distance; // 计算移动速度,将米/秒换算成千米/小时,需要乘以3.6 CGFloat speed = distance / dTime * 3.6; // 计算平均速度 CGFloat avgSpeed = self.sumDistance / self.sumTime * 3.6; NSString * speedFeedback = [NSString stringWithFormat: @"当前速度为:%g千米/小时,平均速度为:%g千米/小时。合计移动:%g千米", speed , avgSpeed , self.sumDistance / 1000]; self.showView.text = speedFeedback; } self.prevLocation = newLocation; } } @end
上面程序中的两行粗体字代码分别用于计算本次定位数据与上次定位数据之间的时间差、距离,用此距离除以时间,即可得到该设备的当前速度。除此之外,该程序还定义了一个sumDistance属性来保存设备移动的总距离,并定义了一个sumTime来保存设备移动的总时间,用设备移动的总距离除以设备移动的总时间,即可获取该设备移动的平均速度。
提示:
iOS系统获取的前后两次定位数据的时间差以秒为单位,前后两次定位数据之间的距离以米为单位,因此直接用距离除以时间得到速度单位为米/秒。如果程序希望以千米/小时作为速度单位,则需要乘以3.6。
编译、运行该程序,并选择“Freeway Driver”来模拟设备携带者在高速公路中驾车,将可以看到该应用显示如图9.4所示的结果。
————本文节选自《疯狂ios讲义(下)》