第十章:使用MapKit开发地图服务

一、使用MapKit框架

MapKit.framework使用MKMapView类代表地图控件,开发者只要在应用界面上添加并显示控件,该应用就可以增加地图。

MapKitView类的常用属性如下:

(1)、 @property (nonatomic) MKMapType mapType;用于设置和返回地图的类型。该属性支持如下

typedef NS_ENUM(NSUInteger, MKMapType) { MKMapTypeStandard = 0,(标准地图) MKMapTypeSatellite,(卫星地图) MKMapTypeHybrid,(混合地图) MKMapTypeSatelliteFlyover NS_ENUM_AVAILABLE(10_11, 9_0), MKMapTypeHybridFlyover NS_ENUM_AVAILABLE(10_11, 9_0), } NS_ENUM_AVAILABLE(10_9, 3_0) __WATCHOS_PROHIBITED;

(2)、 @property (nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;用于设置和返回地图是否可缩进;
(3)、@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;用于设置和返回地图是否可滑动;
(4)、 @property (nonatomic, getter=isRotateEnabled) BOOL rotateEnabled NS_AVAILABLE(10_9, 7_0);用于设置和返回地图是否可旋转;
(5)、 @property (nonatomic) MKCoordinateRegion region;用于设置和返回地图的显示区域;
(6)、 @property (nonatomic) CLLocationCoordinate2D centerCoordinate;用于设置和返回地图的中心位置;
(7)、 @property (nonatomic) MKMapRect visibleMapRect;用于设置和返回地图的显示区域;
(8)、 - (void)addAnnotations:(NSArray

typedef NS_ENUM(NSInteger, MKUserTrackingMode) {
MKUserTrackingModeNone = 0, // the user's location is not followed MKUserTrackingModeFollow, // the map follows the user's location
MKUserTrackingModeFollowWithHeading, // the map follows the user's location and heading } NS_ENUM_AVAILABLE(NA, 5_0) __WATCHOS_PROHIBITED;

(13)、 @property (nonatomic, weak, nullable) id < MKMapViewDelegate > delegate;用于为地图控件指定delegate对象,该对象负责处理地图控件的相关事件;

MKMapView类的主要功能如下:

  • (1)、显示地图,例如显示广州市的地图;
  • (2)、提供多种显示方式,比如标准地图、卫星地图、混合地图;
  • (3)、支持地图的缩放操作;
  • (4)、支持在地图上做标记,例如标记疯狂软件教育中心。也可以在地图上添加复杂的覆盖层;
  • (5)、在地图上定位手机当前所在位置;
  • (6)、可以通过经纬度、地址在地图上进行定位;

MKMapView的delegate属性必须是一个实现MKMapViewDelegate协议的对象,实现MKMapViewDelegate协议时可根据需要实现对应的方法;当MKMapView控件发生某些事件时,该控件的delegate对象(实现MKMapViewDelegate协议)将会自动激发响应的方法,对MKMapView上发生的特定事件进行处理。

当MKMapView控件发生下列事件时,都会激发MKMapViewDelegate对象的相应方法:

  • ①、当MKMapView显示区域发生变化时;
  • ②、当MKMapView加载以及加载完成地图数据时;
  • ③、当MKMapView定位用户位置时;
  • ④、当MKMapView添加标注时;
  • ⑤、当MKMapView控件上的标注位置被改变时;
  • ⑥、当MKMapView控件上的标注被选中或取消选中时;
  • ⑦、当MKMapView控件添加覆盖层或显示覆盖层时;

1、使用MKMapView控件

MKMapView控件位于MapKit.framework中,因此为了在iOS应用中使用该控件,需要完成两件事情:

  • ①、为该应用添加MapKit框架;
  • ②、在需要使用MKMapView以及相关类的源文件中使用#import < MapKit/MapKit.h >
  • ③、导入MapKit.framework

2、指定地图显示中心和显示区域

#import "ViewController.h"

@interface ViewController ()<CLLocationManagerDelegate,MKMapViewDelegate>
{
    CLLocationManager* _locationManager;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //如果定位服务可用
    if ([CLLocationManager locationServicesEnabled]) {
        // 1. 实例化定位管理器
        _locationManager = [[CLLocationManager alloc] init];
        // 2. 设置代理
        _locationManager.delegate = self;
        // 3. 定位精度
        [_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
        // 4.请求用户权限:分为:?只在前台开启定位?在后台也可定位,
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
            [_locationManager requestWhenInUseAuthorization];//?只在前台开启定位
        }
        // 6. 更新用户位置
        [_locationManager startUpdatingLocation];
    }else{
        NSLog(@"无法使用定位服务!!!");
    }
    //设置地图的显示风格,此处设置使用标准地图
    self.mapView.mapType = MKMapTypeStandard;
    //设置地图可收缩
    self.mapView.zoomEnabled = YES;
    //设置地图可滚动
    self.mapView.scrollEnabled = YES;
    //设置地图可旋转
    self.mapView.rotateEnabled = YES;
    //设置显示用户当前位置
    self.mapView.showsUserLocation = YES;
    //设置代理
    self.mapView.delegate = self;
    //调用自己实现的方法设置地图的显示位置和显示区域
    [self locateToLatitude:23.126272 longitude:113.395568];
}
- (void)locateToLatitude:(CGFloat)latitude longitude:(CGFloat)longitude{
    //设置地图中心方式设置经度、纬度
    CLLocationCoordinate2D center = {latitude,longitude};
    //也可以使用如下方法设置经度、纬度
    //center.latitude = latitude;
    //center.longitude = longitude;
    //设置地图显示范围
    MKCoordinateSpan span;
    span.latitudeDelta = 0.01;
    span.longitudeDelta = 0.01;
    //创建MKCoordinateRegion对象,该对象代表地图的显示中心和显示范围
    MKCoordinateRegion region = {center,span};
    //设置当前地图的显示中心和显示范围
    [self.mapView setRegion:region animated:YES];
}
#pragma mark -- MKMapViewDelegate
//MKMapViewDelegate协议中的方法,当MKMapView显示区域将要发生改变时激发该方法
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated{
    NSLog(@"地图控件的显示区域将要发生改变");
}
//MKMapViewDelegate协议中的方法,当MKMapView显示区域完成改变时激发该方法
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
    NSLog(@"地图控件的显示区域完成改变");
}
//MKMapViewDelegate协议中的方法,当MKMapView开始加载数据时激发该方法
- (void)mapViewWillStartLoadingMap:(MKMapView *)mapView{
    NSLog(@"地图控件开始加载地图数据");
}
//MKMapViewDelegate协议中的方法,当MKMapView加载完数据时激发该方法
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView{
    NSLog(@"地图控件加载地图数据完成");
}
//MKMapViewDelegate协议中的方法,当MKMapView加载数据失败时激发该方法
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error{
    NSLog(@"地图控件加载地图数据发生错误,错误信息:%@",error);
}
//MKMapViewDelegate协议中的方法,当MKMapView开始渲染地图时激发该方法
- (void)mapViewWillStartRenderingMap:(MKMapView *)mapView{
    NSLog(@"地图控件开始渲染地图");
}
//MKMapViewDelegate协议中的方法,当MKMapView渲染地图完成时激发该方法
- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered{
    NSLog(@"地图控件渲染地图完成");
}

#pragma mark -- CLLocationManagerDelegate
//成功获取定位数据后将会激发该方法
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    //获取最后一个定位数据
    CLLocation* location = [locations lastObject];
    //依次获取CLLocation中封装的经度、纬度、高度、速度、方向等信息
    NSLog(@"经度:%g,纬度:%g,高度:%g,速度:%g,方向:%g",location.coordinate.latitude,location.coordinate.longitude,location.altitude,location.speed,location.course);
}
//定位失败时激发的方法
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
    NSLog(@"定位失败:%@",error);
}
@end
//2016-06-21 15:47:14.975 地图[489:71686] 地图控件的显示区域将要发生改变
//2016-06-21 15:47:14.976 地图[489:71686] 地图控件的显示区域完成改变
//2016-06-21 15:47:14.981 地图[489:71686] 地图控件的显示区域将要发生改变
//2016-06-21 15:47:14.982 地图[489:71686] 地图控件的显示区域完成改变
//2016-06-21 15:47:15.035 地图[489:71686] 地图控件开始渲染地图
//2016-06-21 15:47:15.112 地图[489:71686] 地图控件开始加载地图数据
//2016-06-21 15:47:20.052 地图[489:71686] 经度:40.005,纬度:116.406,高度:47.4084,速度:-1,方向:-1

3、使用iOS 7 新增的MKMapCamera

iOS 7新增了MKMapCamera作为地图的是『视点』,开发者可以使用MKMapCamera对象在地图中增加一个视点,用于模拟从指定位置、指定高度看向地图的某个点从而为地图增加3D体验。

在地图上使用MKMapCamera如下两步:

  • ①、创建一个MKMapCamera对象,创建MKMapCamera时需要指定该摄像头的经度,纬度和俯视高度;
  • ②、为MKMapView的camera属性指定MKMapCamera对象。
#import "ViewController.h"

@interface ViewController ()
{
    CLLocationManager* _locationManager;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //如果定位服务可用
    if ([CLLocationManager locationServicesEnabled]) {
        // 1. 实例化定位管理器
        _locationManager = [[CLLocationManager alloc] init];
        // 2. 定位精度
        [_locationManager setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
        // 3.请求用户权限:分为:?只在前台开启定位?在后台也可定位,
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
            [_locationManager requestWhenInUseAuthorization];//?只在前台开启定位
        }
        // 4. 更新用户位置
        [_locationManager startUpdatingLocation];
    }else{
        NSLog(@"无法使用定位服务!!!");
    }
    //设置地图的显示风格,此处设置使用标准地图
    self.mapView.mapType = MKMapTypeSatellite;
    //设置地图可收缩
    self.mapView.zoomEnabled = YES;
    //设置地图可滚动
    self.mapView.scrollEnabled = YES;
    //设置地图可旋转
    self.mapView.rotateEnabled = NO;
    //设置显示用户当前位置
    self.mapView.showsUserLocation = YES;
    //调用自己实现的方法设置地图的显示位置和显示区域
    [self locateToLatitude:23.126272 longitude:113.395568];

    CLLocationCoordinate2D to = {23.126272,113.395568};
    CLLocationCoordinate2D from = {22.826272,113.295568};
    MKMapCamera* camera = [MKMapCamera cameraLookingAtCenterCoordinate:to fromEyeCoordinate:from eyeAltitude:10];
    self.mapView.camera = camera;

}
- (void)locateToLatitude:(CGFloat)latitude longitude:(CGFloat)longitude{
    //设置地图中心方式设置经度、纬度
    CLLocationCoordinate2D center = {latitude,longitude};
    //也可以使用如下方法设置经度、纬度
    //center.latitude = latitude;
    //center.longitude = longitude;
    //设置地图显示范围
    MKCoordinateSpan span;
    span.latitudeDelta = 0.01;
    span.longitudeDelta = 0.01;
    //创建MKCoordinateRegion对象,该对象代表地图的显示中心和显示范围
    MKCoordinateRegion region = {center,span};
    //设置当前地图的显示中心和显示范围
    [self.mapView setRegion:region animated:YES];
}
@end

二、根据地址定位

1、地址解析和方向地址解析

由于iOS地图定位必须根据经纬度来完成,因此如果需要程序根据地址进行定位,则需要先把地址解析成经度,纬度。这里涉及如下两个基本概念:

  • (1)、地址解析:把普通用户能看懂的字符串地址转换成经度、纬度;
  • (2)、反向地址解析:把经度,纬度转换成普通的字符串地址;

iOS为地址解析提供了CLGeocoder该工具类提供了如下3个方法来进行地址解析和方向地址解析:

- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;//根据给定的字符串地址进行解析,解析将会割到该地址对应的经度、纬度信息; - (void)geocodeAddressString:(NSString *)addressString inRegion:(CLRegion *)region completionHandler:(CLGeocodeCompletionHandler)completionHandler;//根据给定的字符串地址进行解析,解析将会得到该地址对应的经度、纬度信息。该方法比前一个方法多指定了一个CLRegion类型参数,该参数代表在某个区域内解析,这样可提高解析结果的准确性; - (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;//根据指定的经度、纬度地址反向解析得到字符串地址;

由于上面3个解析方法都是比较耗时的操作,因此iOS将这3个方法都使用代码快设计——当地址解析成功或反向地址解析成功后,系统将会自动执行该方法的第三方参数:代码块,该参数是一个形如如下格式的代码块:

    NSString* text;
    CLGeocoder *geocoder;
    [geocoder geocodeAddressString:text completionHandler:^(NSArray *placemarks, NSError *error) {

    }];

如果解析出现错误,NSError对象就封装了解析过程中的错误信息。如果解析成功,NSError参数为nil,第一个NSArray参数则封装了地址解析或反向地址解析得到的结果。一般来会所,地址解析可能得到多个结果——这是因为全球完全可能有多个同名的地点;但反向地址解析一般只会得到一个结果——因为根据指定经度,纬度得到的地址通常是唯一的。

如果解析成功,NSArray集合的元素为CLPlacemark对象,该对象代表一个定位点,该对象包含如下属性:

1)、@property (nonatomic, readonly, copy) CLLocation *location;//该只读属性返回一个CLLocation类型的值,该值封装了CLPlacemark对象代表经度、纬度信息;2)、@property (nonatomic, readonly, copy) NSString *name;//该只读属性返回CLPlacemark所代表地址的名称;3)、@property (nonatomic, readonly, copy) NSDictionary *addressDictionary;该只读属性返回一个NSDictionary对象,该封装了CLPlacemark所代表地址的详情;

(4)、@property (nonatomic, readonly, copy) NSString *ISOcountryCode;//该只读属性返回CLPlacemark所代表地址所在国家的代码;5)、@property (nonatomic, readonly, copy) NSString *country;//该只读属性返回CLPlacemark所代表地址所在的国家;6)、@property (nonatomic, readonly, copy) NSString *postalCode;//该只读属性返回CLPlacemark所代表地址的编码;7)、@property (nonatomic, readonly, copy) NSString *administrativeArea;//该只读属性返回CLPlacemark所代表地址的行政区域;8)、@property (nonatomic, readonly, copy) NSString *subAdministrativeArea;//该只读属性返回CLPlacemark所代表地址的次级行政区域;9)、@property (nonatomic, readonly, copy) NSString *locality;//该只读属性返回CLPlacemark所代表地址的城市名;10)、@property (nonatomic, readonly, copy) NSString *subLocality;//该只读属性返回CLPlacemark所代表地址的下一级城市名;11)、@property (nonatomic, readonly, copy) NSString *thoroughfare;//该只读属性返回CLPlacemark所代表地址的道路名;12)、@property (nonatomic, readonly, copy) NSString *subThoroughfare;//该只读属性返回CLPlacemark所代表地址的下一级道路名;

地址解析

self.geocoder_01 = [[CLGeocoder alloc]init];
[self.geocoder_01 geocodeAddressString:@"北京市海淀区田村40号院" completionHandler:^(NSArray *placemarks, NSError *error) {
    //如果解析结果的结合元素个数大于0,则表明解析得到了经度 纬度信息
    if (placemarks.count > 0) {
        //只处理一个解析结果,实际项目中可使用列表让用户选择;
        for (CLPlacemark* placemark in placemarks) {
            NSLog(@"经度为:%g,纬度为:%g",placemark.location.coordinate.longitude,placemark.location.coordinate.latitude);
        }
    }else{
        NSLog(@"error = %@",error);
        NSLog(@"无结果");
    }
}];

反向地址解析

self.geocoder_02 = [[CLGeocoder alloc]init];
CLLocation* location = [[CLLocation alloc]initWithLatitude:39.9286 longitude:116.327];
[self.geocoder_02 reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
    //如果解析结果的结合元素个数大于0,则表明解析得到了地址信息
    if (placemarks.count > 0) {
        //只处理一个解析结果,实际项目中可使用列表让用户选择;
        for (CLPlacemark* placemark in placemarks) {
            NSArray* addrArray = [placemark.addressDictionary objectForKey:@"FormattedAddressLines"];
            NSMutableString* addr = [NSMutableString string];
            for (NSString* str in addrArray) {
                [addr appendString:str];
            }
            NSLog(@"%@",placemark.addressDictionary);
            NSLog(@"经度为:%g,纬度为:%g,地址为:%@",placemark.location.coordinate.longitude,placemark.location.coordinate.latitude,addr);
        }
    }else{
        NSLog(@"error = %@",error);
        NSLog(@"无结果");
    }
}];
经度为:116.333,纬度为:39.9299,地址为:中国北京市海淀区甘家口街道百万庄大街22-2号楼

注意
①、CLGeocoder属于CoreLocation.framework,
开发者需要为项目添加CoreLocation框架
②、在使用CLGeocoder的代码中使用#import < CoreLocation/CoreLocation.h >

2、根据地址定位

根据地址定位的思路非常简单,只要如下两步即可:

  • ①、使用CLGeocoder根据字符串地址得到该地址的经度、纬度;
  • ②、根据解析得到的经度、纬度进行定位;

三、在地图上添加锚点

很多时候,我们希望在地图上添加一些锚点(就是一个图标,当用户点击图标时显示该地点的详细信息)来标识重要的地点,只要调用MKMapView的addAnnotation:方法即可为地图添加锚点。

1、添加简单的锚点

- (void)addAnnotation:(id <MKAnnotation>)annotation;

上面方法中MKAnnotation是一个协议,该协议中定义了3个属性,用于设置和返回锚点的信息;

1)、@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;//用于设置和返回锚点的位置。该属性值必须是一个CLLocationCoordinate2D结构体变量,封装了经度、纬度信息;2)、@property (nonatomic, readonly, copy) NSString *title;//用于设置和返回锚点的标题;3)、@property (nonatomic, readonly, copy) NSString *subtitle;//用于设置和返回锚点的副标题

iOS为MKAnnotation协议提供了一个实现类:MKPointAnnotation,该实现类实现了MKAnnotation的全部方法,因此通常可使用MKPointAnnotation来添加锚点:

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()
{
    CLLocationManager* _locationManager;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //如果定位服务可用
    if ([CLLocationManager locationServicesEnabled]) {
        // 1. 实例化定位管理器
        _locationManager = [[CLLocationManager alloc] init];
        // 2. 定位精度
        [_locationManager setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
        // 3.请求用户权限:分为:?只在前台开启定位?在后台也可定位,
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
            [_locationManager requestWhenInUseAuthorization];//?只在前台开启定位
        }
        // 4. 更新用户位置
        [_locationManager startUpdatingLocation];
    }else{
        NSLog(@"无法使用定位服务!!!");
    }
    //设置地图的显示风格,此处设置使用标准地图
    self.mapView.mapType = MKMapTypeSatellite;
    //设置地图可收缩
    self.mapView.zoomEnabled = YES;
    //设置地图可滚动
    self.mapView.scrollEnabled = YES;
    //设置地图可旋转
    self.mapView.rotateEnabled = YES;
    //设置显示用户当前位置
    self.mapView.showsUserLocation = YES;
    //调用自己实现的方法设置地图的显示位置和显示区域
    [self locateToLatitude:23.126272 longitude:113.395568];
}
- (void)locateToLatitude:(CGFloat)latitude longitude:(CGFloat)longitude{
    //设置地图中心方式设置经度、纬度
    CLLocationCoordinate2D center = {latitude,longitude};
    //也可以使用如下方法设置经度、纬度
    //center.latitude = latitude;
    //center.longitude = longitude;
    //设置地图显示范围
    MKCoordinateSpan span;
    span.latitudeDelta = 0.01;
    span.longitudeDelta = 0.01;
    //创建MKCoordinateRegion对象,该对象代表地图的显示中心和显示范围
    MKCoordinateRegion region = {center,span};
    //设置当前地图的显示中心和显示范围
    [self.mapView setRegion:region animated:YES];
    //创建MKPointAnnotation对象——代表一个锚点
    MKPointAnnotation* annotation = [[MKPointAnnotation alloc]init];
    annotation.title = @"正标题";
    annotation.subtitle = @"我是副标题,代表正标题的位置";
    CLLocationCoordinate2D coordinate = {latitude,longitude};
    annotation.coordinate = coordinate;
    //添加锚点
    [self.mapView addAnnotation:annotation];
}

@end

2、添加自定义锚点

对于iOS的地图而言,锚点实际上由两个部分组成:

  • (1)、锚点信息:锚点信息包括锚点的位置、标题、副标题等信息,这些信息由MKAnnotation对象代表;
  • (2)、锚点控件:锚点控件决定地图上显示的锚点外观,包括锚点的图片等;

iOS 为锚点控件提供了MKAnnotationView和MKPinAnnotationView,其中MKPinAnnotationView是MKAnnotationView的子类,而MKAnnotationView继承了UIView——也就是说,它只是一个可视的UI控件。

MKAnnotationView与MKPinAnnotationView的区别在于,MKPinAnnotationView已经为锚点控件选择了图片。因此,如果打算只用MKPinAnnotationView作为锚点控件,那么该锚点控件将总是显示大头针图片,开发者只能修改大头针的颜色;但如果需要自己定制锚点的图片,则可考虑使用MKAnnotationView,该控件允许指定一个image属性,该属性用于定制锚点控件的图片。

定制地图上的锚点,需要完成如下两步:

(1)、为MKMapView指定delegate对象;
(2)、重写delegate的

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation

方法,该方法的返回值将作为地图上的描点控件;

例如:

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<MKMapViewDelegate>
{
    CLLocationManager* _locationManager;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //如果定位服务可用
    if ([CLLocationManager locationServicesEnabled]) {
        // 1. 实例化定位管理器
        _locationManager = [[CLLocationManager alloc] init];
        // 2. 定位精度
        [_locationManager setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
        // 3.请求用户权限:分为:?只在前台开启定位?在后台也可定位,
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
            [_locationManager requestWhenInUseAuthorization];//?只在前台开启定位
        }
        // 4. 更新用户位置
        [_locationManager startUpdatingLocation];
    }else{
        NSLog(@"无法使用定位服务!!!");
    }
    //设置地图的显示风格,此处设置使用标准地图
    self.mapView.mapType = MKMapTypeSatellite;
    //设置地图可收缩
    self.mapView.zoomEnabled = YES;
    //设置地图可滚动
    self.mapView.scrollEnabled = YES;
    //设置地图可旋转
    self.mapView.rotateEnabled = YES;
    //设置显示用户当前位置
    self.mapView.showsUserLocation = YES;
    self.mapView.delegate = self;
    //调用自己实现的方法设置地图的显示位置和显示区域
    [self locateToLatitude:23.126272 longitude:113.395568];
}
- (void)locateToLatitude:(CGFloat)latitude longitude:(CGFloat)longitude{
    //设置地图中心方式设置经度、纬度
    CLLocationCoordinate2D center = {latitude,longitude};
    //也可以使用如下方法设置经度、纬度
    //center.latitude = latitude;
    //center.longitude = longitude;
    //设置地图显示范围
    MKCoordinateSpan span;
    span.latitudeDelta = 0.01;
    span.longitudeDelta = 0.01;
    //创建MKCoordinateRegion对象,该对象代表地图的显示中心和显示范围
    MKCoordinateRegion region = {center,span};
    //设置当前地图的显示中心和显示范围
    [self.mapView setRegion:region animated:YES];

    //创建MKPointAnnotation对象——代表一个锚点
    MKPointAnnotation* annotation = [[MKPointAnnotation alloc]init];
    annotation.title = @"正标题";
    annotation.subtitle = @"我是副标题,代表正标题的位置";
    CLLocationCoordinate2D coordinate = {latitude,longitude};
    annotation.coordinate = coordinate;
    //添加锚点
    [self.mapView addAnnotation:annotation];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
    static NSString* annoId = @"fkAnno";
    //获取可重用的锚点
    MKAnnotationView* annoView = [mapView dequeueReusableAnnotationViewWithIdentifier:annoId];
    //如果可重用的锚点控件不存在,则创建新的可重用锚点控件
    if (!annoView) {
        annoView = [[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:annoId];
        /** * 如果不想改变锚点控件的图片,只想改变颜色,则可创建MKPinAnnotationVeiw实例,再修改MKPinAnnotationVeiw对象的pinColor属性即可 */
    }
    //为锚点控件设置图片
    annoView.image = [UIImage imageNamed:@"Unknown.png"];
    //设置该锚点控件是否可显示起泡信息
    annoView.canShowCallout = YES;
    //定义一个按钮,用于锚点控件设置附加控件
    UIButton* button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
    //为按钮绑定事件处理方法
    [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
    //可通过锚点控件的rightCalloutAccessoryView、leftCalloutAccessoryView设置附加控件
    annoView.rightCalloutAccessoryView = button;
    return annoView;
}
- (void)buttonTapped:(UIButton *)btn{
    NSLog(@"111");
}

四、在地图上添加覆盖层

除了在地图上添加简单的锚点之外,有些时候我们还希望向地图上添加一些丰富的信息,比如某个公园的边界、汽车经过的路径、汽车导航的路线,这些信息可通过覆盖层来实现。
MKMapView提供了如下方法来添加覆盖层:

1)、- (void)addOverlay:(id <MKOverlay>)overlay level:(MKOverlayLevel)level NS_AVAILABLE(10_9, 7_0);//将单个的覆盖层添加到指定的层级;2)、- (void)addOverlays:(NSArray *)overlays level:(MKOverlayLevel)level NS_AVAILABLE(10_9, 7_0);//将多个覆盖层添加到指定的层级;3)、- (void)removeOverlay:(id <MKOverlay>)overlay NS_AVAILABLE(10_9, 4_0);//将单个的覆盖层添加到默认层级;4)、- (void)removeOverlays:(NSArray *)overlays NS_AVAILABLE(10_9, 4_0);//将多个覆盖层添加到默认层级;

上面的四个方法中都涉及覆盖层的概念,iOS使用MKOverlay来代表覆盖层。在iOS 7以前,所有的覆盖层都只能添加到默认层级——也就是说,上面4个方法中前两个是iOS 7新增的。从iOS7以后,覆盖层可以被添加到指定的层级,这样就可以将覆盖层放置在相关数据的上面或者下面。

上面四个方法中前两个都需要传入一个MLOverlayLevel类型的参数,该枚举类型支持MKOverlayLevelAboveRoads、MKOverlayLevelAboveLabels两个枚举。

MKOverlay只是一个协议,因此iOS还为该协议提供了如下实现类:
(1)、MKCircle:代表一个圆形覆盖层;
(2)、MKPolygon:代表一个多边形覆盖层,可用于标识公园的边界等;
(3)、MKPolyline:代表一个多线段覆盖层,可用于标识汽车经过的路径、汽车导航的路线等;
(4)、MKTileOverlay:代表使用位图平铺的覆盖层。这是iOS 7新增的API;

覆盖层与地图锚点有相同的设计,每个覆盖层实际上由两部分信息组成:
(1)、覆盖层的位置、集合形状等信息;
(2)、覆盖层控件;

覆盖层与覆盖层控件的对应关系如下:
(1)、MKCircle对应的覆盖层控件为MKCircleView;
(2)、MKPolygon对应的覆盖层控件为MKPolygonView;
(3)、MKPolyline对应的覆盖层控件为MKPolylineView;
(4)、MKTileOverlay是iOS 7新增的,没有对应的覆盖层控件;

iOS 7推荐使用MKXxxRenderer来负责渲染覆盖层控件。

掌握上面这些覆盖层与覆盖层控件、覆盖层Renderer之间的关系之后,接下来即可通过这些API在地图上添加覆盖层。添加覆盖层也很简单,只要如下3步即可。

  1. 创建MKOverlay的实现类的实例,并设置必要的信息;
  2. 调用MKMapView的添加层的方法将MKOverlay添加进入;
  3. 重写MKMapView的delegate的mapView:rendererForOverlay:方法,该放阿飞返回MKOverlayRenderer将会控制生成地图上覆盖层控件;

提示:
如果使用iOS 7以前的版本,不是重写delegate的mapView:rendererForOverlay:方法,而是重写delegate的mapView:viewForOverlay:方法,该方法返回的MKOverlayView将会作为覆盖层控件。从iOS 7开始,该方法过时了,不再推荐使用。

1、添加几何覆盖层

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<MKMapViewDelegate>
{
    CLLocationManager* _locationManager;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //如果定位服务可用
    if ([CLLocationManager locationServicesEnabled]) {
        // 1. 实例化定位管理器
        _locationManager = [[CLLocationManager alloc] init];
        // 2. 定位精度
        [_locationManager setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
        // 3.请求用户权限:分为:?只在前台开启定位?在后台也可定位,
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
            [_locationManager requestWhenInUseAuthorization];//?只在前台开启定位
        }
        // 4. 更新用户位置
        [_locationManager startUpdatingLocation];
    }else{
        NSLog(@"无法使用定位服务!!!");
    }
    //设置地图的显示风格,此处设置使用标准地图
    self.mapView.mapType = MKMapTypeSatellite;
    //设置地图可收缩
    self.mapView.zoomEnabled = YES;
    //设置地图可滚动
    self.mapView.scrollEnabled = YES;
    //设置地图可旋转
    self.mapView.rotateEnabled = YES;
    //设置显示用户当前位置
    self.mapView.showsUserLocation = YES;
    //设置代理
    self.mapView.delegate = self;
    //调用自己实现的方法设置地图的显示位置和显示区域
    [self locateToLatitude:23.126272 longitude:113.395568];

    //创建一个长按手势
    UILongPressGestureRecognizer* gesture = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
    [self.mapView addGestureRecognizer:gesture];
}
- (void)locateToLatitude:(CGFloat)latitude longitude:(CGFloat)longitude{
    //设置地图中心方式设置经度、纬度
    CLLocationCoordinate2D center = {latitude,longitude};
    //也可以使用如下方法设置经度、纬度
    //center.latitude = latitude;
    //center.longitude = longitude;
    //设置地图显示范围
    MKCoordinateSpan span;
    span.latitudeDelta = 0.01;
    span.longitudeDelta = 0.01;
    //创建MKCoordinateRegion对象,该对象代表地图的显示中心和显示范围
    MKCoordinateRegion region = {center,span};
    //设置当前地图的显示中心和显示范围
    [self.mapView setRegion:region animated:YES];
}
- (void)longPress:(UILongPressGestureRecognizer *)gesture{
    //获取长按点的坐标
    CGPoint pos = [gesture locationInView:self.mapView];
    //将长按点的坐标转换成经纬度值
    CLLocationCoordinate2D coord = [self.mapView convertPoint:pos toCoordinateFromView:self.mapView];
    //创建MKCircle对象,该对象代表覆盖层
    MKCircle* circle = [MKCircle circleWithCenterCoordinate:coord radius:100];
    //添加MKOverlay
    [self.mapView addOverlay:circle level:MKOverlayLevelAboveLabels];
}
#pragma mark -- MKMapViewDelegate
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{
    MKCircle* circle = (MKCircle *)overlay;
    //创建一个MKCircleRenderer对象
    MKCircleRenderer* render = [[MKCircleRenderer alloc]initWithCircle:circle];
    //设置MKCirleRenderer的透明度
    render.alpha = 0.3;
    //设置MKCirleRenderer的填充颜色和边框颜色
    render.fillColor = [UIColor blueColor];
    render.strokeColor = [UIColor redColor];
    return render;
}
@end

2、使用iOS 7新增的MKTileOverlay覆盖层

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<MKMapViewDelegate>
{
    CLLocationManager* _locationManager;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //如果定位服务可用
    if ([CLLocationManager locationServicesEnabled]) {
        // 1. 实例化定位管理器
        _locationManager = [[CLLocationManager alloc] init];
        // 2. 定位精度
        [_locationManager setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
        // 3.请求用户权限:分为:?只在前台开启定位?在后台也可定位,
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
            [_locationManager requestWhenInUseAuthorization];//?只在前台开启定位
        }
        // 4. 更新用户位置
        [_locationManager startUpdatingLocation];
    }else{
        NSLog(@"无法使用定位服务!!!");
    }
    //设置地图的显示风格,此处设置使用标准地图
    self.mapView.mapType = MKMapTypeStandard;
    //设置地图可收缩
    self.mapView.zoomEnabled = YES;
    //设置地图可滚动
    self.mapView.scrollEnabled = YES;
    //设置地图可旋转
    self.mapView.rotateEnabled = YES;
    //设置显示用户当前位置
    self.mapView.showsUserLocation = YES;
    //设置代理
    self.mapView.delegate = self;
    //调用自己实现的方法设置地图的显示位置和显示区域
    [self locateToLatitude:23.126272 longitude:113.395568];

    //创建一个长按手势
    UILongPressGestureRecognizer* gesture = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
    [self.mapView addGestureRecognizer:gesture];
}
- (void)locateToLatitude:(CGFloat)latitude longitude:(CGFloat)longitude{
    //设置地图中心方式设置经度、纬度
    CLLocationCoordinate2D center = {latitude,longitude};
    //也可以使用如下方法设置经度、纬度
    //center.latitude = latitude;
    //center.longitude = longitude;
    //设置地图显示范围
    MKCoordinateSpan span;
    span.latitudeDelta = 0.01;
    span.longitudeDelta = 0.01;
    //创建MKCoordinateRegion对象,该对象代表地图的显示中心和显示范围
    MKCoordinateRegion region = {center,span};
    //设置当前地图的显示中心和显示范围
    [self.mapView setRegion:region animated:YES];
}
- (void)longPress:(UILongPressGestureRecognizer *)gesture{
    NSURL* url = [[NSBundle mainBundle]URLForResource:@"Unknown" withExtension:@"png"];
    //创建MKTileOverlay对象,该对象代表覆盖层
    MKTileOverlay* overlay = [[MKTileOverlay alloc]initWithURLTemplate:[url description]];
    //添加MKOverlay
    [self.mapView addOverlay:overlay];
}
#pragma mark -- MKMapViewDelegate
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{
    //创建MKTileOverlayRenderer对象
    MKTileOverlayRenderer* renderer = [[MKTileOverlayRenderer alloc]initWithOverlay:(MKTileOverlay *)overlay];
    //设置MKTileOverlayRenderer的透明度为0.3
    renderer.alpha = 0.3;
    return renderer;
}
@end

五、使用iOS 7新增的MKDirections获取导航路线

iOS新增了MKDirections类,该类可通过MKDirectionsRequest向Apple服务器发送导航请求,Apple服务器将会返回一条或多条导航路线,然后通过覆盖层把其中一条或多条导航的路线绘制在地图上,这样就可以提醒用户进行导航。

在最简单的情况下,程序只要简单的显示当前用户的位置,MKMapView上就会显示用户当前已经行走到哪里,这样就可以实现一个简单的导航软件了。

提示:
更实际的导航软件可能会使用汽车图片来代表用户当前位置——这很简单,因为用户当前位置也就是一锚点,可以通过重写MKMapView的delegate的mapView:viewForAnnotation:方法来改变代表用户当前位置的锚点图片。除此之外,还可以通过CLLocationManager监听用户位置的改变,让地图始终以用户当前位置为中心,甚至可通过CLLocationManager获取用户移动的方向,从而控制代表用户当前位置的汽车图标始终以车头向前的方式显示。

使用MKDirections获取导航路线只要如下3步:

  • (1)、创建MKDirectionsRequest对象,并为该对象设置导航起始点、导航结束点等必要信息。除此之外,还可为该对象设置transportType属性,该属性指定用户移动的交通工具,该属性支持如下3个枚举值之一
typedef NS_OPTIONS(NSUInteger, MKDirectionsTransportType) {
    MKDirectionsTransportTypeAutomobile     = 1 << 0,//汽车
    MKDirectionsTransportTypeWalking        = 1 << 1,//步行
    MKDirectionsTransportTypeAny            = 0x0FFFFFFF//任意
} NS_ENUM_AVAILABLE(10_9, 7_0);
  • (2)、以MKDirectionsRequest为参数,创建MKDirections对象;
  • (3)、调用MKDirections的如下放方法请求导航路线。调用该方法时需要传入一个代码块——当该对象成功获取导航路线之后将会激发该代码块。
- (void)calculateDirectionsWithCompletionHandler:(MKDirectionsHandler)completionHandler;

行车导航仪

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<MKMapViewDelegate,CLLocationManagerDelegate>
{
    CLLocationManager* _locationManager;
}
//定义一个CLGeocoder对象,该对象负责对用户输入的地址进行解析
@property (nonatomic ,strong) CLGeocoder* geocoder;
//定义一个变量来保存导航路线
@property (nonatomic ,strong) MKPolyline* naviPath;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //如果定位服务可用
    if ([CLLocationManager locationServicesEnabled]) {
        // 1. 实例化定位管理器
        _locationManager = [[CLLocationManager alloc] init];
        // 2. 定位精度
        _locationManager.delegate = self;
        [_locationManager setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
        // 3.请求用户权限:分为:?只在前台开启定位?在后台也可定位,
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
            [_locationManager requestWhenInUseAuthorization];//?只在前台开启定位
        }
        // 4. 更新用户位置
        [_locationManager startUpdatingLocation];
    }else{
        NSLog(@"无法使用定位服务!!!");
    }
    //设置地图的显示风格,此处设置使用标准地图
    self.mapView.mapType = MKMapTypeStandard;
    //设置地图可收缩
    self.mapView.zoomEnabled = YES;
    //设置地图可滚动
    self.mapView.scrollEnabled = YES;
    //设置地图可旋转
    self.mapView.rotateEnabled = YES;
    //设置显示用户当前位置
    self.mapView.showsUserLocation = YES;
    //设置代理
    self.mapView.delegate = self;
    //调用自己实现的方法设置地图的显示位置和显示区域
    [self locateToLatitude:40.005 longitude:116.406];

    self.geocoder = [[CLGeocoder alloc]init];
    //解析目标地址,获取实际的经度、纬度信息
    [self.geocoder geocodeAddressString:@"北京市朝阳区华悦国际" completionHandler:^(NSArray *placemarks, NSError *error) {
        /** * 在实际应用中,此处如果发西安placemarkd集合包含多个元素,即标明根据该地址字符串检索到多个地址值,那么应该显示一个列表框让用户选择目标地址,此处为了简化该示例,直接使用第一个地址值作为目标地址 */
        if (placemarks.count > 0) {
            //删除上一条的导航路线
            [self.mapView removeOverlay:self.naviPath];
            //创建MKDirectionsReqyest对象,作为查询导航路线的请求
            MKDirectionsRequest* request = [[MKDirectionsRequest alloc]init];
            //将当前位置设置为导航的起始点
            request.source = [MKMapItem mapItemForCurrentLocation];
            //获取地址解析得到的一个地址值
            CLPlacemark* clPlacemark = placemarks[0];
            MKPlacemark* mkPlacemark = [[MKPlacemark alloc]initWithPlacemark:clPlacemark];
            //将解析得到的目标设置为导航的结束点
            request.destination = [[MKMapItem alloc]initWithPlacemark:mkPlacemark];
            //以MKDirectionsReqyest作为参数,创建MKDirections对象
            MKDirections* directions = [[MKDirections alloc]initWithRequest:request];
            [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
                if (response.routes.count > 0) {
                    //获取查询得到的导航信息
                    MKRoute* route = response.routes[0];
                    //保存检索得到的导航信息
                    self.naviPath = route.polyline;
                    //将self.naviPath(MKPolyline对象)添加成覆盖层
                    [self.mapView addOverlay:self.naviPath level:MKOverlayLevelAboveLabels];
                }
            }];
        }
    }];
}
- (void)locateToLatitude:(CGFloat)latitude longitude:(CGFloat)longitude{
    //设置地图中心方式设置经度、纬度
    CLLocationCoordinate2D center = {latitude,longitude};
    //也可以使用如下方法设置经度、纬度
    //center.latitude = latitude;
    //center.longitude = longitude;
    //设置地图显示范围
    MKCoordinateSpan span;
    span.latitudeDelta = 0.01;
    span.longitudeDelta = 0.01;
    //创建MKCoordinateRegion对象,该对象代表地图的显示中心和显示范围
    MKCoordinateRegion region = {center,span};
    //设置当前地图的显示中心和显示范围
    [self.mapView setRegion:region animated:YES];
}

#pragma mark -- MKMapViewDelegate
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{
    //创建MKPolylineRenderer对象,该对象负责绘制MKPolyline覆盖层控件
    MKPolylineRenderer* renderer = [[MKPolylineRenderer alloc]initWithPolyline:overlay];
    //设置MKPolylineRenderer对象的线条颜色
    renderer.strokeColor = [UIColor blueColor];
    //设置MKPloylineRenderer的线宽度
    renderer.lineWidth = 2;
    return renderer;
}
#pragma mark -- CLLocationManagerDelegate
//成功获取定位数据后将会激发该方法
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    //获取最后一个定位数据
    CLLocation* location = [locations lastObject];
    //依次获取CLLocation中封装的经度、纬度、高度、速度、方向等信息
    NSLog(@"经度:%g,纬度:%g,高度:%g,速度:%g,方向:%g",location.coordinate.latitude,location.coordinate.longitude,location.altitude,location.speed,location.course);
}
//定位失败时激发的方法
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
    NSLog(@"定位失败:%@",error);
}
@end

六、本章小结

本章详细介绍了iOS系统的MapKit框架。MapKit框架的主要作用就是在iOS应用中增加地图功能,MapKit框架的基础部分就是MKMapView、MKMapViewDelegate以及MKMapCamera,通过这些API即可在应用中增加地图并处理地图加载的相关事件。除此之外,希望在地图上添加锚点,则需要借助于MKAnnotation协议(包括MKPointAnnotation实现类)、MKAnnotationView和MKPinAnnotationView;如果希望在地图上添加覆盖层,则需要借助于MKOverlay协议(包括各种实现类)和MKOverlayRenderer的各种子类(iOS 7以前使用MKOverlayView以及各种子类);
本章最后还介绍了iOS 7新增的MKDirections类,通过该类可以获取到啊好难过路线信息。

你可能感兴趣的:(地图,MkMapView,MapKit)