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的朋友需要稍作调整。
#import "KCMainViewController.h" #import <CoreLocation/CoreLocation.h>
KCMainViewController ()<CLLocationManagerDelegate>{
CLLocationManager *_locationManager;
}
@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
地理编码
除了提供位置跟踪功能之外,在定位服务中还包含CLGeocoder类用于处理地理编码和逆地理编码(又叫反地理编码)功能。
地理编码:根据给定的位置(通常是地名)确定地理坐标(经、纬度)。
逆地理编码:可以根据地理坐标(经、纬度)确定位置信息(街道、门牌等)。
CLGeocoder最主要的两个方法就是- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;和- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;,分别用于地理编码和逆地理编码。下面简单演示一下:
#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中进行地图开发主要有两种方式,一种是直接利用MapKit框架进行地图开发,利用这种方式可以对地图进行精准的控制;另一种方式是直接调用苹果官方自带的地图应用,主要用于一些简单的地图应用(例如:进行导航覆盖物填充等),无法进行精确的控制
用户位置跟踪
在很多带有地图的应用中默认打开地图都会显示用户当前位置,同时将当前位置标记出来放到屏幕中点方便用户对周围情况进行查看。如果在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是一个模型对象,对于多数应用模型会稍有不同,例如后面的内容中会给大头针模型对象添加其他属性)。 KCMainViewController.m 的 #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
KCAnnotation.h的
#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
扩展--自定义大头针弹详情视图
通过上面的示例不难看出MKAnnotationView足够强大(何况还有MKPinAnnotationView),很多信息都可以进行设置,但是唯独不能修改大头针描述详情视图(仅仅支持详情中左右视图内容)。要实现这个需求目前开发中普遍采用的思路就是:
a.点击一个大头针A时重新在A的坐标处添加另一个大头针B(注意此时将A对应的大头针视图canShowCallout设置为false)作为大头针详情模型,然后在- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;代理方法中判断大头针类型,如果是B则重写MKAnnotationView(可以自定义一个类C继承于MKAnnotationView),返回自定义大头针视图C。
b.定义大头针视图C继承于MKAnnotationView(或者MKPinAnnotationView),在自定义大头针视图中添加自己的控件,完成自定义布局。 大头针模型: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;
#pragma mark 自定义一个图片属性在创建大头针视图时使用 @property (nonatomic,strong) UIImage *image;
#pragma mark 大头针详情左侧图标 @property (nonatomic,strong) UIImage *icon; #pragma mark 大头针详情描述 @property (nonatomic,copy) NSString *detail; #pragma mark 大头针右下方星级评价 @property (nonatomic,strong) UIImage *rate;
@end 弹出详情大头针模型:KCCalloutAnnotation.h
// // KCCalloutAnnotation.h // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //
#import <UIKit/UIKit.h> #import <CoreLocation/CoreLocation.h> #import <MapKit/MapKit.h>
@interface KCCalloutAnnotation : NSObject<MKAnnotation>
@property (nonatomic) CLLocationCoordinate2D coordinate; @property (nonatomic, copy,readonly) NSString *title; @property (nonatomic, copy,readonly) NSString *subtitle;
#pragma mark 左侧图标 @property (nonatomic,strong) UIImage *icon; #pragma mark 详情描述 @property (nonatomic,copy) NSString *detail; #pragma mark 星级评价 @property (nonatomic,strong) UIImage *rate;
@end 弹出详情大头针视图:KCCalloutAnnotatonView.h
// // KCCalloutView.h // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // 自定义弹出标注视图
#import <UIKit/UIKit.h> #import <CoreLocation/CoreLocation.h> #import <MapKit/MapKit.h> #import "KCCalloutAnnotation.h"
@interface KCCalloutAnnotationView : MKAnnotationView
@property (nonatomic ,strong) KCCalloutAnnotation *annotation;
#pragma mark 从缓存取出标注视图 +(instancetype)calloutViewWithMapView:(MKMapView *)mapView;
@end KCCalloutAnnotationView.m
// // KCCalloutView.m // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //
#import "KCCalloutAnnotationView.h" #define kSpacing 5 #define kDetailFontSize 12 #define kViewOffset 80
@interface KCCalloutAnnotationView(){ UIView *_backgroundView; UIImageView *_iconView; UILabel *_detailLabel; UIImageView *_rateView; }
@end
@implementation KCCalloutAnnotationView
-(instancetype)init{ if(self=[super init]){ [self layoutUI]; } return self; } -(instancetype)initWithFrame:(CGRect)frame{ if (self=[super initWithFrame:frame]) { [self layoutUI]; } return self; }
-(void)layoutUI{ //背景 _backgroundView=[[UIView alloc]init]; _backgroundView.backgroundColor=[UIColor whiteColor]; //左侧添加图标 _iconView=[[UIImageView alloc]init];
//上方详情_detailLabel=[[UILabel alloc]init];_detailLabel.lineBreakMode=NSLineBreakByWordWrapping;//[_text sizeToFit];_detailLabel.font=[UIFont systemFontOfSize:kDetailFontSize];//下方星级_rateView=[[UIImageView alloc]init];[self addSubview:_backgroundView];[self addSubview:_iconView];[self addSubview:_detailLabel];[self addSubview:_rateView];
}
+(instancetype)calloutViewWithMapView:(MKMapView *)mapView{ static NSString *calloutKey=@"calloutKey1"; KCCalloutAnnotationView *calloutView=(KCCalloutAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:calloutKey]; if (!calloutView) { calloutView=[[KCCalloutAnnotationView alloc]init]; } return calloutView; }
#pragma mark 当给大头针视图设置大头针模型时可以在此处根据模型设置视图内容 -(void)setAnnotation:(KCCalloutAnnotation *)annotation{ [super setAnnotation:annotation]; //根据模型调整布局 _iconView.image=annotation.icon; _iconView.frame=CGRectMake(kSpacing, kSpacing, annotation.icon.size.width, annotation.icon.size.height);
_detailLabel.text=annotation.detail;float detailWidth=150.0;CGSize detailSize= [annotation.detail boundingRectWithSize:CGSizeMake(detailWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kDetailFontSize]} context:nil].size;float detailX=CGRectGetMaxX(_iconView.frame)+kSpacing;_detailLabel.frame=CGRectMake(detailX, kSpacing, detailSize.width, detailSize.height);_rateView.image=annotation.rate;_rateView.frame=CGRectMake(detailX, CGRectGetMaxY(_detailLabel.frame)+kSpacing, annotation.rate.size.width, annotation.rate.size.height);float backgroundWidth=CGRectGetMaxX(_detailLabel.frame)+kSpacing;float backgroundHeight=_iconView.frame.size.height+2*kSpacing;_backgroundView.frame=CGRectMake(0, 0, backgroundWidth, backgroundHeight);self.bounds=CGRectMake(0, 0, backgroundWidth, backgroundHeight+kViewOffset);
} @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" #import "KCCalloutAnnotationView.h" #import "KCCalloutAnnotationView.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; annotation1.image=[UIImage imageNamed:@"icon_pin_floating.png"]; annotation1.icon=[UIImage imageNamed:@"icon_mark1.png"]; annotation1.detail=@"CMJ Studio..."; annotation1.rate=[UIImage imageNamed:@"icon_Movie_Star_rating.png"]; [_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;annotation2.image=[UIImage imageNamed:@"icon_paopao_waterdrop_streetscape.png"];annotation2.icon=[UIImage imageNamed:@"icon_mark2.png"];annotation2.detail=@"Kenshin Cui...";annotation2.rate=[UIImage imageNamed:@"icon_Movie_Star_rating.png"];[_mapView addAnnotation:annotation2];
}
#pragma mark - 地图控件代理方法 #pragma mark 显示大头针时调用,注意方法中的annotation参数是即将显示的大头针对象 -(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{ //由于当前位置的标注也是一个大头针,所以此时需要判断,此代理方法返回nil使用默认大头针视图 if ([annotation isKindOfClass:[KCAnnotation class]]) { static NSString *key1=@"AnnotationKey1"; MKAnnotationView *annotationView=[_mapView dequeueReusableAnnotationViewWithIdentifier:key1]; //如果缓存池中不存在则新建 if (!annotationView) { annotationView=[[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:key1]; // annotationView.canShowCallout=true;//允许交互点击 annotationView.calloutOffset=CGPointMake(0, 1);//定义详情视图偏移量 annotationView.leftCalloutAccessoryView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_classify_cafe.png"]];//定义详情左侧视图 }
//修改大头针视图 //重新设置此类大头针视图的大头针模型(因为有可能是从缓存池中取出来的,位置是放到缓存池时的位置) annotationView.annotation=annotation; annotationView.image=((KCAnnotation *)annotation).image;//设置大头针视图的图片 return annotationView;}else if([annotation isKindOfClass:[KCCalloutAnnotation class]]){ //对于作为弹出详情视图的自定义大头针视图无弹出交互功能(canShowCallout=false,这是默认值),在其中可以自由添加其他视图(因为它本身继承于UIView) KCCalloutAnnotationView *calloutView=[KCCalloutAnnotationView calloutViewWithMapView:mapView]; calloutView.annotation=annotation; return calloutView;} else { return nil;}
}
#pragma mark 选中大头针时触发 //点击一般的大头针KCAnnotation时添加一个大头针作为所点大头针的弹出详情视图 -(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{ KCAnnotation *annotation=view.annotation; if ([view.annotation isKindOfClass:[KCAnnotation class]]) { //点击一个大头针时移除其他弹出详情视图 // [self removeCustomAnnotation]; //添加详情大头针,渲染此大头针视图时将此模型对象赋值给自定义大头针视图完成自动布局 KCCalloutAnnotation *annotation1=[[KCCalloutAnnotation alloc]init]; annotation1.icon=annotation.icon; annotation1.detail=annotation.detail; annotation1.rate=annotation.rate; annotation1.coordinate=view.annotation.coordinate; [mapView addAnnotation:annotation1]; } }
#pragma mark 取消选中时触发 -(void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{ [self removeCustomAnnotation]; }
#pragma mark 移除所用自定义大头针 -(void)removeCustomAnnotation{ [_mapView.annotations enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj isKindOfClass:[KCCalloutAnnotation class]]) { [_mapView removeAnnotation:obj]; } }]; } @end 在这个过程中需要注意几点:
1.大头针A作为一个普通大头针,其中最好保存自定义大头针视图C所需要的模型以便根据不同的模型初始化视图。
2.自定义大头针视图C的大头针模型B中不需要title、subtitle属性,最好设置为只读;模型中最后保存自定义大头针视图C所需要的布局模型数据。
3.只有点击非B类大头针时才新增自定义大头针,并且增加时要首先移除其他B类大头针避免重叠(一般建议放到取消大头针选择的代理方法中)。
4.通常在自定义大头针视图C设置大头针模型时布局界面,此时需要注意新增大头针的位置,通常需要偏移一定的距离才能达到理想的效果。