定位功能
定位是一个很常用的功能,如一些地图软件打开之后如果用户允许软件定位的话,那么打开软件后就会自动锁定到当前位置,如果用户手机移动那么当前位置也会跟随着变化。要实现这个功能需要使用Core Loaction中CLLocationManager类,首先看一下这个类的一些主要方法和属性:
类方法 | 说明 |
+ (BOOL)locationServicesEnabled; | 是否启用定位服务,通常如果用户没有启用定位服务可以提示用户打开定位服务 |
+ (CLAuthorizationStatus)authorizationStatus; | 定位服务授权状态,返回枚举类型: kCLAuthorizationStatusNotDetermined: 用户尚未做出决定是否启用定位服务 kCLAuthorizationStatusRestricted: 没有获得用户授权使用定位服务,可能用户没有自己禁止访问授权 kCLAuthorizationStatusDenied :用户已经明确禁止应用使用定位服务或者当前系统定位服务处于关闭状态 kCLAuthorizationStatusAuthorizedAlways: 应用获得授权可以一直使用定位服务,即使应用不在使用状态 kCLAuthorizationStatusAuthorizedWhenInUse: 使用此应用过程中允许访问定位服务 |
属性 | 说明 |
desiredAccuracy | 定位精度,枚举类型: kCLLocationAccuracyBest:最精确定位 |
distanceFilter | 位置信息更新最小距离,只有移动大于这个距离才更新位置信息,默认为kCLDistanceFilterNone:不进行距离限制 |
对象方法 | 说明 |
startUpdatingLocation | 开始定位追踪,开始定位后将按照用户设置的更新频率执行-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;方法反馈定位信息 |
stopUpdatingLocation | 停止定位追踪 |
startUpdatingHeading | 开始导航方向追踪 |
stopUpdatingHeading | 停止导航方向追踪 |
startMonitoringForRegion: | 开始对某个区域进行定位追踪,开始对某个区域进行定位后。如果用户进入或者走出某个区域会调用- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region和- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region代理方法反馈相关信息 |
stopMonitoringForRegion: | 停止对某个区域进行定位追踪 |
requestWhenInUseAuthorization | 请求获得应用使用时的定位服务授权,注意使用此方法前在要在info.plist中配置NSLocationWhenInUseUsageDescription |
requestAlwaysAuthorization | 请求获得应用一直使用定位服务授权,注意使用此方法前要在info.plist中配置NSLocationAlwaysUsageDescription |
代理方法 | 说明 |
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations; | 位置发生改变后执行(第一次定位到某个位置之后也会执行) |
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading; | 导航方向发生变化后执行 |
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region | 进入某个区域之后执行 |
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region | 走出某个区域之后执行 |
iOS 8 还提供了更加人性化的定位服务选项。App 的定位服务不再仅仅是关闭或打开,现在,定位服务的启用提供了三个选项,「永不」「使用应用程序期间」和「始终」。同时,考虑到能耗问题,如果一款 App 要求始终能在后台开启定位服务,iOS 8 不仅会在首次打开 App 时主动向你询问,还会在日常使用中弹窗提醒你该 App 一直在后台使用定位服务,并询问你是否继续允许。在iOS7及以前的版本,如果在应用程序中使用定位服务只要在程序中调用startUpdatingLocation方法应用就会询问用户是否允许此应用是否允许使用定位服务,同时在提示过程中可以通过在info.plist中配置通过配置Privacy - Location Usage Description告诉用户使用的目的,同时这个配置是可选的。
但是在iOS8中配置配置项发生了变化,可以通过配置NSLocationAlwaysUsageDescription或者NSLocationWhenInUseUsageDescription来告诉用户使用定位服务的目的,并且注意这个配置是必须的,如果不进行配置则默认情况下应用无法使用定位服务,打开应用不会给出打开定位服务的提示,除非安装后自己设置此应用的定位服务。同时,在应用程序中需要根据配置对requestAlwaysAuthorization或locationServicesEnabled方法进行请求。由于本人机器已经更新到最新的iOS8.1下面的内容主要针对iOS8,使用iOS7的朋友需要稍作调整。
// // KCMainViewController.m // CoreLocation // // Created by Kenshin Cui on 14-03-27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" #import <CoreLocation/CoreLocation.h> @interface KCMainViewController ()<CLLocationManagerDelegate>{ CLLocationManager *_locationManager; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; //定位管理器 _locationManager=[[CLLocationManager alloc]init]; if (![CLLocationManager locationServicesEnabled]) { NSLog(@"定位服务当前可能尚未打开,请设置打开!"); return; } //如果没有授权则请求用户授权 if ([CLLocationManager authorizationStatus]==kCLAuthorizationStatusNotDetermined){ [_locationManager requestWhenInUseAuthorization]; }else if([CLLocationManager authorizationStatus]==kCLAuthorizationStatusAuthorizedWhenInUse){ //设置代理 _locationManager.delegate=self; //设置定位精度 _locationManager.desiredAccuracy=kCLLocationAccuracyBest; //定位频率,每隔多少米定位一次 CLLocationDistance distance=10.0;//十米定位一次 _locationManager.distanceFilter=distance; //启动跟踪定位 [_locationManager startUpdatingLocation]; } } #pragma mark - CoreLocation 代理 #pragma mark 跟踪定位代理方法,每次位置发生变化即会执行(只要定位到相应位置) //可以通过模拟器设置一个虚拟位置,否则在模拟器中无法调用此方法 -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ CLLocation *location=[locations firstObject];//取出第一个位置 CLLocationCoordinate2D coordinate=location.coordinate;//位置坐标 NSLog(@"经度:%f,纬度:%f,海拔:%f,航向:%f,行走速度:%f",coordinate.longitude,coordinate.latitude,location.altitude,location.course,location.speed); //如果不需要实时定位,使用完即使关闭定位服务 [_locationManager stopUpdatingLocation]; } @end
注意:
1.定位频率和定位精度并不应当越精确越好,需要视实际情况而定,因为越精确越耗性能,也就越费电。
2.定位成功后会根据设置情况频繁调用-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations方法,这个方法返回一组地理位置对象数组,每个元素一个CLLocation代表地理位置信息(包含经度、纬度、海报、行走速度等信息),之所以返回数组是因为有些时候一个位置点可能包含多个位置。
3.使用完定位服务后如果不需要实时监控应该立即关闭定位服务以节省资源。
4.除了提供定位功能,CLLocationManager还可以调用startMonitoringForRegion:方法对指定区域进行监控。
地理编码
除了提供位置跟踪功能之外,在定位服务中还包含CLGeocoder类用于处理地理编码和逆地理编码(又叫反地理编码)功能。
地理编码:根据给定的位置(通常是地名)确定地理坐标(经、纬度)。
逆地理编码:可以根据地理坐标(经、纬度)确定位置信息(街道、门牌等)。
CLGeocoder最主要的两个方法就是- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;和- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;,分别用于地理编码和逆地理编码。下面简单演示一下:
// // KCMainViewController.m // CoreLocation // // Created by Kenshin Cui on 14-03-27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" #import <CoreLocation/CoreLocation.h> @interface KCMainViewController ()<CLLocationManagerDelegate>{ CLGeocoder *_geocoder; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; _geocoder=[[CLGeocoder alloc]init]; [self getCoordinateByAddress:@"北京"]; [self getAddressByLatitude:39.54 longitude:116.28]; } #pragma mark 根据地名确定地理坐标 -(void)getCoordinateByAddress:(NSString *)address{ //地理编码 [_geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) { //取得第一个地标,地标中存储了详细的地址信息,注意:一个地名可能搜索出多个地址 CLPlacemark *placemark=[placemarks firstObject]; CLLocation *location=placemark.location;//位置 CLRegion *region=placemark.region;//区域 NSDictionary *addressDic= placemark.addressDictionary;//详细地址信息字典,包含以下部分信息 // NSString *name=placemark.name;//地名 // NSString *thoroughfare=placemark.thoroughfare;//街道 // NSString *subThoroughfare=placemark.subThoroughfare; //街道相关信息,例如门牌等 // NSString *locality=placemark.locality; // 城市 // NSString *subLocality=placemark.subLocality; // 城市相关信息,例如标志性建筑 // NSString *administrativeArea=placemark.administrativeArea; // 州 // NSString *subAdministrativeArea=placemark.subAdministrativeArea; //其他行政区域信息 // NSString *postalCode=placemark.postalCode; //邮编 // NSString *ISOcountryCode=placemark.ISOcountryCode; //国家编码 // NSString *country=placemark.country; //国家 // NSString *inlandWater=placemark.inlandWater; //水源、湖泊 // NSString *ocean=placemark.ocean; // 海洋 // NSArray *areasOfInterest=placemark.areasOfInterest; //关联的或利益相关的地标 NSLog(@"位置:%@,区域:%@,详细信息:%@",location,region,addressDic); }]; } #pragma mark 根据坐标取得地名 -(void)getAddressByLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude{ //反地理编码 CLLocation *location=[[CLLocation alloc]initWithLatitude:latitude longitude:longitude]; [_geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *placemark=[placemarks firstObject]; NSLog(@"详细信息:%@",placemark.addressDictionary); }]; } @end
地图
iOS从6.0开始地图数据不再由谷歌驱动,而是改用自家地图,当然在国内它的数据是由高德地图提供的。这样一来,如果在iOS6.0之前进行地图开发的话使用方法会有所不同,基于目前的情况其实使用iOS6.0之前版本的系统基本已经寥寥无几了,所有在接下来的内容中不会再针对iOS5及之前版本的地图开发进行介绍。
在iOS中进行地图开发主要有两种方式,一种是直接利用MapKit框架进行地图开发,利用这种方式可以对地图进行精准的控制;另一种方式是直接调用苹果官方自带的地图应用,主要用于一些简单的地图应用(例如:进行导航覆盖物填充等),无法进行精确的控制。当然,本节重点内容还是前者,后面的内容也会稍加提示。
用MapKit之前需要简单了解一下MapKit中地图展示控件MKMapView的的一些常用属性和方法,具体如下表:属性 | 说明 |
userTrackingMode | 跟踪类型,是一个枚举: MKUserTrackingModeNone :不进行用户位置跟踪; MKUserTrackingModeFollow :跟踪用户位置; MKUserTrackingModeFollowWithHeading :跟踪用户位置并且跟踪用户前进方向; |
mapType | 地图类型,是一个枚举: MKMapTypeStandard :标准地图,一般情况下使用此地图即可满足; MKMapTypeSatellite :卫星地图; MKMapTypeHybrid :混合地图,加载最慢比较消耗资源; |
userLocation | 用户位置,只读属性 |
annotations | 当前地图中的所有大头针,只读属性 |
对象方法 | 说明 |
- (void)addAnnotation:(id <MKAnnotation>)annotation; | 添加大头针,对应的有添加大头针数组 |
- (void)removeAnnotation:(id <MKAnnotation>)annotation; | 删除大头针,对应的有删除大头针数组 |
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated; | 设置地图显示区域,用于控制当前屏幕显示地图范围 |
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated; | 设置地图中心点位置 |
- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view; | 将地理坐标(经纬度)转化为数学坐标(UIKit坐标) |
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view; | 将数学坐标转换为地理坐标 |
- (MKAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier; | 从缓存池中取出大头针,类似于UITableView中取出UITableViewCell,为了进行性能优化而设计 |
- (void)selectAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated; | 选中指定的大头针 |
- (void)deselectAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated; | 取消选中指定的大头针 |
代理方法 | 说明 |
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation ; | 用户位置发生改变时触发(第一次定位到用户位置也会触发该方法) |
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation ; | 显示区域发生改变后触发 |
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView; | 地图加载完成后触发 |
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation; | 显示大头针时触发,返回大头针视图,通常自定义大头针可以通过此方法进行 |
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view | 点击选中某个大头针时触发 |
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view | 取消选中大头针时触发 |
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay | 渲染地图覆盖物时触发 |
用户位置跟踪
在很多带有地图的应用中默认打开地图都会显示用户当前位置,同时将当前位置标记出来放到屏幕中点方便用户对周围情况进行查看。如果在iOS6或者iOS7中实现这个功能只需要添加地图控件、设置用户跟踪模式、在-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation代理方法中设置地图中心区域及显示范围。但是在iOS8中用法稍有不同:
1.由于在地图中进行用户位置跟踪需要使用定位功能,而定位功能在iOS8中设计发生了变化,因此必须按照前面定位章节中提到的内容进行配置和请求。
2.iOS8中不需要进行中心点的指定,默认会将当前位置设置中心点并自动设置显示区域范围。
了解以上两点,要进行用户位置跟踪其实就相当简单了,值得一提的是-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation这个代理方法。这个方法只有在定位(利用前面章节中的定位内容)到当前位置之后就会调用,以后每当用户位置发生改变就会触发,调用频率相当频繁。
大头针
在iOS开发中经常会标记某个位置,需要使用地图标注,也就是大家俗称的“大头针”。只要一个NSObject类实现MKAnnotation协议就可以作为一个大头针,通常会重写协议中coordinate(标记位置)、title(标题)、subtitle(子标题)三个属性,然后在程序中创建大头针对象并调用addAnnotation:方法添加大头针即可(之所以iOS没有定义一个基类实现这个协议供开发者使用,多数原因应该是MKAnnotation是一个模型对象,对于多数应用模型会稍有不同,例如后面的内容中会给大头针模型对象添加其他属性)。
KCAnnotation.h
// // KCAnnotation.h // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import <MapKit/MapKit.h> @interface KCAnnotation : NSObject<MKAnnotation> @property (nonatomic) CLLocationCoordinate2D coordinate; @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *subtitle; @end
KCMainViewController.m
// // KCMainViewController.m // MapKit Annotation // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // 37.785834 -122.406417 // 39.92 116.39 #import "KCMainViewController.h" #import <CoreLocation/CoreLocation.h> #import <MapKit/MapKit.h> #import "KCAnnotation.h" @interface KCMainViewController ()<MKMapViewDelegate>{ CLLocationManager *_locationManager; MKMapView *_mapView; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self initGUI]; } #pragma mark 添加地图控件 -(void)initGUI{ CGRect rect=[UIScreen mainScreen].bounds; _mapView=[[MKMapView alloc]initWithFrame:rect]; [self.view addSubview:_mapView]; //设置代理 _mapView.delegate=self; //请求定位服务 _locationManager=[[CLLocationManager alloc]init]; if(![CLLocationManager locationServicesEnabled]||[CLLocationManager authorizationStatus]!=kCLAuthorizationStatusAuthorizedWhenInUse){ [_locationManager requestWhenInUseAuthorization]; } //用户位置追踪(用户位置追踪用于标记用户当前位置,此时会调用定位服务) _mapView.userTrackingMode=MKUserTrackingModeFollow; //设置地图类型 _mapView.mapType=MKMapTypeStandard; //添加大头针 [self addAnnotation]; } #pragma mark 添加大头针 -(void)addAnnotation{ CLLocationCoordinate2D location1=CLLocationCoordinate2DMake(39.95, 116.35); KCAnnotation *annotation1=[[KCAnnotation alloc]init]; annotation1.title=@"CMJ Studio"; annotation1.subtitle=@"Kenshin Cui's Studios"; annotation1.coordinate=location1; [_mapView addAnnotation:annotation1]; CLLocationCoordinate2D location2=CLLocationCoordinate2DMake(39.87, 116.35); KCAnnotation *annotation2=[[KCAnnotation alloc]init]; annotation2.title=@"Kenshin&Kaoru"; annotation2.subtitle=@"Kenshin Cui's Home"; annotation2.coordinate=location2; [_mapView addAnnotation:annotation2]; } #pragma mark - 地图控件代理方法 #pragma mark 更新用户位置,只要用户改变则调用此方法(包括第一次定位到用户位置) -(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{ NSLog(@"%@",userLocation); //设置地图显示范围(如果不进行区域设置会自动显示区域范围并指定当前用户位置为地图中心点) // MKCoordinateSpan span=MKCoordinateSpanMake(0.01, 0.01); // MKCoordinateRegion region=MKCoordinateRegionMake(userLocation.location.coordinate, span); // [_mapView setRegion:region animated:true]; } @end