ios定位


概要:

如今非常多社交、电商、团购应用都引入了地图和定位功能。似乎地图功能不再是地图应用和导航应用所特有的。的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们的生活方式。比如你到了一个陌生的地方想要查找附近的酒店、超市等就能够打开软件搜索周边;类似的,还有非常多团购软件能够依据你所在的位置自己主动为你推荐某些商品。

总之。眼下地图和定位功能已经大量引入到应用开发中。

今天就和大家一起看一下iOS怎样进行地图和定位开发。


定位:

要实现地图、导航功能,往往须要先熟悉定位功能,在iOS中通过Core Location框架进行定位操作。

Core Location自身能够单独使用,和地图开发框架MapKit全然是独立的。可是往往地图开发要配合定位框架使用。在Core Location中主要包括了定位、地理编码(包括反编码)功能。

定位功能

定位是一个非经常常使用的功能。如一些地图软件打开之后假设用户同意软件定位的话,那么打开软件后就会自己主动锁定到当前位置,假设用户手机移动那么当前位置也会尾随着变化。

要实现这个功能须要使用Core Loaction中CLLocationManager类,首先看一下这个类的一些主要方法和属性:

类方法 说明
+ (BOOL)locationServicesEnabled; 是否启用定位服务,通常假设用户没有启用定位服务能够提示用户打开定位服务
+ (CLAuthorizationStatus)authorizationStatus; 定位服务授权状态,返回枚举类型:
kCLAuthorizationStatusNotDetermined: 用户尚未做出决定是否启用定位服务
kCLAuthorizationStatusRestricted: 没有获得用户授权使用定位服务,可能用户没有自己禁止訪问授权
kCLAuthorizationStatusDenied :用户已经明白禁止应用使用定位服务或者当前系统定位服务处于关闭状态
kCLAuthorizationStatusAuthorizedAlways: 应用获得授权能够一直使用定位服务,即使应用不在使用状态
kCLAuthorizationStatusAuthorizedWhenInUse: 使用此应用过程中同意訪问定位服务
属性 说明
desiredAccuracy 定位精度。枚举类型:

kCLLocationAccuracyBest:最精确定位
CLLocationAccuracy kCLLocationAccuracyNearestTenMeters:十米误差范围
kCLLocationAccuracyHundredMeters:百米误差范围
kCLLocationAccuracyKilometer:千米误差范围
kCLLocationAccuracyThreeKilometers:三千米误差范围

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的朋友须要稍作调整。


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;,分别用于地理编码和逆地理编码。以下简单演示一下:



#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

//
//

#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

//

#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

执行效果:

MapKit_Annotation 

设置大头针视图

在一些应用中系统默认的大头针样式可能无法满足实际的需求。此时就须要改动大头针视图默认样式。

依据前面MapKit的代理方法不难发现- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;方法能够返回一个大头针视图,仅仅要实现这种方法并在这种方法中定义一个大头针视图MKAnnotationView对象并设置相关属性就能够改变默认大头针的样式。MKAnnotationView经常使用属性:

属性 说明
annotation 大头针模型信息。包含标题、子标题、地理位置。

image 大头针图片
canShowCallout 点击大头针是否显示标题、子标题内容等,注意假设在- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;方法中又一次定义大头针默认情况是无法交互的须要设置为true。
calloutOffset 点击大头针时弹出详情信息视图的偏移量
selected 是否被选中状态
leftCalloutAccessoryView 弹出详情左側视图
rightCalloutAccessoryView 弹出详情右側视图

须要注意:

a.这个代理方法的调用时机:每当有大头针显示到系统可视界面中时就会调用此方法返回一个大头针视图放到界面中。同一时候当前系统位置标注(也就是地图中蓝色的位置点)也是一个大头针,也会调用此方法,因此处理大头针视图时须要差别对待。

b.类似于UITableView的代理方法,此方法调用频繁,开发过程中须要反复利用MapKit的缓存池将大头针视图缓存起来反复利用。

c.自己定义大头针默认情况下不同意交互,假设交互须要设置canShowCallout=true

d.假设代理方法返回nil则会使用默认大头针视图,须要依据情况设置。

以下以一个演示样例进行大头针视图设置。这里设置了大头针的图片、弹出视图、偏移量等信息。

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;

#pragma mark 自己定义一个图片属性在创建大头针视图时使用
@property (nonatomic,strong) UIImage *image;

@end

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;
    annotation1.image=[UIImage imageNamed:@"icon_pin_floating.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"];
    [_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 {
        return nil;
    }
}
@end

执行效果:

MapKit_CustomeAnnotationImage

注意:

在MapKit框架中除了MKAnnotationView之外另一个MKPinAnnotationView,它是MKAnnotationView的子类,相比MKAnnotationView多了两个属性pinColor和animationDrop,分别用于设置大头针视图颜色和加入大头针动画。

扩展--自己定义大头针弹详情视图

通过上面的演示样例不难看出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),在自己定义大头针视图中加入自己的控件。完毕自己定义布局。

在使用百度地图client时当点击一个搜索位置时能够看到此位置的评价等信息,视图效果大概例如以下:

Baidu_CalloutView 

以下最好还是试着实现一下这个效果:

大头针模型: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;

#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


//

#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



#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



#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


#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设置大头针模型时布局界面,此时须要注意新增大头针的位置。通常须要偏移一定的距离才干达到理想的效果。

执行效果:

MapKit_CustomAnotationCalloutView2

使用自带的地图应用

除了能够使用MapKit框架进行地图开发,对地图有精确的控制和自己定义之外,假设对于应用没有特殊要求的话选用苹果自带的地图应用也是一个不错的选择。使用苹果自带的应用时须要用到MapKit中的MKMapItem类,这个类有一个openInMapsWithLaunchOptions:动态方法和一个openMapsWithItems: launchOptions:静态方法用于打开苹果地图应用。第一个方法用于在地图上标注一个位置,第二个方法除了能够标注多个位置外还能够进行多个位置之间的驾驶导航,使用起来也是相当方便。在熟悉这两个方法使用之前有必要对两个方法中的options參数做一下简单说明:

键(常量) 说明
MKLaunchOptionsDirectionsModeKey 路线模式,常量 MKLaunchOptionsDirectionsModeDriving  驾车模式
MKLaunchOptionsDirectionsModeWalking 步行模式
MKLaunchOptionsMapTypeKey 地图类型。枚举 MKMapTypeStandard :标准模式
MKMapTypeSatellite :卫星模式
MKMapTypeHybrid  :混合模式
MKLaunchOptionsMapCenterKey 中心点坐标,CLLocationCoordinate2D类型  
MKLaunchOptionsMapSpanKey 地图显示跨度,MKCoordinateSpan 类型  
MKLaunchOptionsShowsTrafficKey 是否 显示交通状况,布尔型  
MKLaunchOptionsCameraKey 3D地图效果。MKMapCamera类型
注意:此属性从iOS7及以后可用,前面的属性从iOS6開始可用
 

单个位置的标注

以下的代码演示了怎样在苹果自带地图应用上标记一个位置,首先依据反地理编码获得一个CLPlacemark位置对象,然后将其转换为MKPlacemark对象用于MKMapItem初始化。最后调用其openInMapsWithLaunchOptions:打开地图应用并标记:

#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>@interface KCMainViewController ()@property (nonatomic,strong) CLGeocoder *geocoder;@end@implementation KCMainViewController- (void)viewDidLoad { [super viewDidLoad]; _geocoder=[[CLGeocoder alloc]init]; [self location];}#pragma mark 在地图上定位-(void)location{ //依据“北京市”进行地理编码 [_geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark=[placemarks firstObject];//获取第一个地标 MKPlacemark *mkplacemark=[[MKPlacemark alloc]initWithPlacemark:clPlacemark];//定位地标转化为地图的地标 NSDictionary *options=@{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard)}; MKMapItem *mapItem=[[MKMapItem alloc]initWithPlacemark:mkplacemark]; [mapItem openInMapsWithLaunchOptions:options]; }];}@end

执行效果:

AppleMap_Placemark

标记多个位置

假设要标记多个位置须要调用MKMapItem的静态方法。以下的代码演示中须要注意,使用CLGeocoder进行定位时一次仅仅能定位到一个位置,所以第二个位置定位放到了第一个位置获取成功之后。

 #import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>@interface KCMainViewController ()@property (nonatomic,strong) CLGeocoder *geocoder;@end@implementation KCMainViewController- (void)viewDidLoad { [super viewDidLoad]; _geocoder=[[CLGeocoder alloc]init]; [self listPlacemark];}-(void)listPlacemark{ //依据“北京市”进行地理编码 [_geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark1=[placemarks firstObject];//获取第一个地标 MKPlacemark *mkPlacemark1=[[MKPlacemark alloc]initWithPlacemark:clPlacemark1]; //注意地理编码一次仅仅能定位到一个位置,不能同一时候定位。所在放到第一个位置定位完毕回调函数中再次定位 [_geocoder geocodeAddressString:@"郑州市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark2=[placemarks firstObject];//获取第一个地标 MKPlacemark *mkPlacemark2=[[MKPlacemark alloc]initWithPlacemark:clPlacemark2]; NSDictionary *options=@{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard)}; //MKMapItem *mapItem1=[MKMapItem mapItemForCurrentLocation];//当前位置 MKMapItem *mapItem1=[[MKMapItem alloc]initWithPlacemark:mkPlacemark1]; MKMapItem *mapItem2=[[MKMapItem alloc]initWithPlacemark:mkPlacemark2]; [MKMapItem openMapsWithItems:@[mapItem1,mapItem2] launchOptions:options]; }]; }];}@end

执行效果:

ApleMap_MultiPlacemark3

地图导航

要使用地图导航功能在自带地图应用中相当简单,仅仅要设置參数配置导航模式就可以,比如在上面代码基础上设置驾驶模式,则地图应用会启动驾驶模式计算两点之间的距离同一时候对路线进行规划。



#import "KCMainViewController.h"
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>

@interface KCMainViewController ()
@property (nonatomic,strong) CLGeocoder *geocoder;
@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _geocoder=[[CLGeocoder alloc]init];
    
    [self turnByTurn];
}

-(void)turnByTurn{
    //依据“北京市”地理编码
    [_geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) {
        CLPlacemark *clPlacemark1=[placemarks firstObject];//获取第一个地标
        MKPlacemark *mkPlacemark1=[[MKPlacemark alloc]initWithPlacemark:clPlacemark1];
        //注意地理编码一次仅仅能定位到一个位置,不能同一时候定位,所在放到第一个位置定位完毕回调函数中再次定位
        [_geocoder geocodeAddressString:@"郑州市" completionHandler:^(NSArray *placemarks, NSError *error) {
            CLPlacemark *clPlacemark2=[placemarks firstObject];//获取第一个地标
            MKPlacemark *mkPlacemark2=[[MKPlacemark alloc]initWithPlacemark:clPlacemark2];
            NSDictionary *options=@{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard),MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeDriving};
            //MKMapItem *mapItem1=[MKMapItem mapItemForCurrentLocation];//当前位置
            MKMapItem *mapItem1=[[MKMapItem alloc]initWithPlacemark:mkPlacemark1];
            MKMapItem *mapItem2=[[MKMapItem alloc]initWithPlacemark:mkPlacemark2];
            [MKMapItem openMapsWithItems:@[mapItem1,mapItem2] launchOptions:options];
            
        }];
        
    }];
}
@end


原文地址:https://www.cnblogs.com/gccbuaa/p/6908077.html