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进行定位的关键就是CLLocationManager对象及其delegate对象。其中CLLocationManager负责获取定位信息而delegate则负责处理定位事件——通过这些事件即可获取设备所在位置。
CLLocationManager还提供了如下类方法来判断当前设备的定位相关服务状态。
+ locationServicesEnabled返回当前定位服务是否可用。
+ deferredLocationUpdatesAvailable返回延迟定位更新是否可用。
+ significantLocationChangeMonitoringAvailable返回重大位置改变监听是否可用。
+ headingAvailable返回该设备是否支持磁力计计算方向。
+ isRangingAvailable返回蓝牙信号范围服务是否可用。这是iOS 7新增的方法。
除此之外在使用CLLocationManager开始定位之前还可为该对象设置如下属性。
pausesLocationUpdatesAutomatically设置iOS设备是否可暂停定位来节省电池的电量。如果该属性设为“YES”则当iOS设备不再需要定位数据时iOS设备可以自动暂停定位。
distanceFilter设置CLLocationManager的自动过滤距离。也就是说只有当设备在水平方向的位置改变超过该数值以米为单位指定的距离时才会生成一次位置改变的信号。
desiredAccuracy设置定位服务的精度。该属性值支持kCLLocationAccuracyBestForNavigation导航级的最佳精确度、kCLLocationAccuracyBest最佳精确度、kCLLocationAccuracy NearestTenMeters10米误差、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 ()@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@interface FKViewController () @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讲义下》