引言

位置定位几乎是App中的不可或缺的功能,功能分为两部分:一部分是使用CoreLocation框架来获取地理位置经纬度;另一部分:使用MapKit将经纬度以UI的形式展示出来。

使用场景

  • 导航软件(设定起点和终点进行路线规划并指引用户到达目的地如 百度地图、高德地图)
  • 获取用户所在的城市(如美团切换城市)
  • 采集用户的信息(如统计App使用的地理位置分布)
  • 查找周边服务(周边好友、周边美食、周边KTV、附近影院等,如美团、大众点评)

地理定位CoreLocation

地理定位分为前台定位和后台定位,使用CoreLocation在iOS8.0之前、iOS8.0、iOS9.0不完全一样:

  • 前台模式:只能在打开App的时候使用定位
  • 后台模式:Background Mode, App打开或者App退到后台模式都可以使用定位

CoreLocation框架包含的功能:

  • 地理定位
  • 地理编码
  • 区域监听

MapKit

MapKit框架常用的功能有两个:

  • 地图展示:在地图上添加一些大头针、路线等展示给用户看
  • 路线规划:

CoreLocation和MapKit比较

CoreLocation的功能用户获取地理数据
MapKit的功能是将CoreLocation获取的数据使用MapKit框架以UI的形式展示出来


CoreLocation使用详解

CoreLocation使用(iOS8.0之前)

  1. ViewController代码
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>

@interface ViewController () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *localManager;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    _localManager = [[CLLocationManager alloc] init];
    _localManager.delegate = self;
    [_localManager startUpdatingLocation];     // 开始定位
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
    NSLog(@"%@", locations);

    [self.localManager stopUpdatingLocation];  // 停止定位
}
@end

2. Info.plist配置

  • 隐私配置:配置使用定位描述
<key>NSLocationUsageDescription</key>
<string>请允许使用定位以便为您提供更好的服务</string>
  • 配置后台模式(可选):默认情况下当App退入到后台模式时是不不进行定位的
<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

可以以源码的方式直接在Info.plist中写该键值对,也可以通过Xcode提供的界面来配置, 通过Xcode界面配置的实质也就是在Info.plist中配置UIBackgroundModes键,两种配置方式,实质是一样的。

iOS CoreLocation和MapKit详解_后台定位

iOS CoreLocation和MapKit详解_后台定位_02

3.运行结果

允许弹框


CoreLocation使用(iOS8.0之后)

iOS8.0之后系统不会主动请求定位授权了,需要程序员手动写代码请求授权
[_localManager requestWhenInUseAuthorization]; // 请求前台授权
[_localManager requestAlwaysAuthorization]; // 请求总是授权(前后台授权)

1. ViewController

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>

@interface ViewController () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *localManager;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.localManager startUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
    NSLog(@"%@", locations);

    [self.localManager stopUpdatingLocation];
}

- (CLLocationManager *)localManager {
    if (_localManager == nil) {
        _localManager = [[CLLocationManager alloc] init];
        _localManager.delegate = self;
        if ([[UIDevice currentDevice].systemVersion floatValue] > 8.0) {
            [_localManager requestWhenInUseAuthorization];  // 请求前台授权
            //[_localManager requestAlwaysAuthorization];   // 请求总是授权(前后台授权)
        }
    }

    return _localManager;
}
@end

2. Info.plist配置

iOS CoreLocation和MapKit详解_#import_03

<key>NSLocationWhenInUseUsageDescription</key>
<string>iOS8.0+请求前台定位授权</string>

3. 运行结果

iOS CoreLocation和MapKit详解_后台定位_04

可以通过模拟器Debug—>Location 来改变当前地理位置

iOS CoreLocation和MapKit详解_定位服务_05

4. 请求前后台定位

在iOS8.0之后后台定位有两种方式:

  • 方式一:请求前台定位,调用[_localManager requestWhenInUseAuthorization];,如果想要后台定位需要勾选后台定位模式 ,后台定位时主屏幕会有个蓝条一直闪烁

注意:如果iOS8.0+(例如iPhone6 Plus - iOS8.1)的模拟器没有地理定位可能是模拟器的问题,此时需要将模拟器的Debug–>Location 改为None,然后再改成Custom Location 即可定位。此种方式有一个不好的地方就是在后台定位时,主屏幕最上方会有一个蓝条在一直闪烁提示正在后台定位,体验非常不好。

iOS CoreLocation和MapKit详解_定位服务_06

  • 方式二:请求前后台定位,调用[localManager requestAlwaysAuthorization]; 无需勾选后台模式,该方式主屏幕上没有蓝条提示闪烁
  1. 首先代码要调用 [localManager requestAlwaysAuthorization];
  2. 需要在Info.plist添加key:
<key>NSLocationAlwaysUsageDescription</key>
<string>iOS8.0+请求前后台定位授权</string>
  1. 运行效果

用户能否定位最终取决于用户自己的设置,如果允许定位为默认为始终,如果用户设置为用不那位置定位功能就不能使用了。总共有三项可以选择,这些选项可以任意的去掉,有些应用只有两项,要么“永不”,要么“始终”,把 “使用应用期间”这个选项去掉了。

iOS CoreLocation和MapKit详解_定位服务_07


CoreLocation使用(iOS9.0之后)

iOS9.0之后相对于iOS8.0之后的变化是:变就是iOS8.0+的后台定位中方式一,除了要勾选后台模式以为还要再设置一个属性等于YES,_localManager.allowsBackgroundLocationUpdates = YES;
同样这种方式仍然在主屏幕最上方有蓝色条一直闪烁提示正在后台定位

- (CLLocationManager *)localManager {
    if (_localManager == nil) {
        _localManager = [[CLLocationManager alloc] init];
        _localManager.delegate = self;
        if ([[UIDevice currentDevice].systemVersion floatValue] > 8.0) {
            [_localManager requestWhenInUseAuthorization];  // 请求前台授权
            if ([[UIDevice currentDevice].systemVersion floatValue] > 9.0) {
              // iOS9.0+ 设置允许后台位置更新
              _localManager.allowsBackgroundLocationUpdates = YES;
            }
        }
    }

    return _localManager;
}

iOS CoreLocation和MapKit详解_定位服务_08


CoreLocation其他属性

1. 使用代理方法监听授权状态的改变locationManager:didChangeAuthorizationStatus:

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>

@interface ViewController () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *localManager;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}
// 开始定位,入股定位服务关闭或者拒绝定位就跳转到相应的设置界面引导用户设置
- (IBAction)startLocation{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
    NSString *urlStr = nil;
    if ([CLLocationManager locationServicesEnabled]) {
        urlStr = UIApplicationOpenSettingsURLString;
    } else {
        // iOS10.0+ 打不开报错,解决办法:http://www.jianshu.com/p/ee666b21e0a9
        urlStr = @"prefs:root=Privacy&path=LOCATION";
    }
    if(status == kCLAuthorizationStatusDenied){
        // 定位服务开启了
        NSLog(@"永不或者不允许Not Allow");

        // 手动跳转到设置允许定位
        if ([[UIDevice currentDevice].systemVersion floatValue] > 8.0) {
            NSURL *url = [NSURL URLWithString:urlStr];
            if ([[UIApplication sharedApplication] canOpenURL:url]) {
                [[UIApplication sharedApplication] openURL:url];
            }
        }
    } else {
         [self.localManager startUpdatingLocation];
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
    NSLog(@"%@", locations);
    CLLocation * location = [locations lastObject];
    location.coordinate; // 经纬度
    location.altitude; // 海拔信息
    location.horizontalAccuracy; // 水平精度,如果值是复数代表无效
    location.verticalAccuracy; // 垂直精度, ,如果值是复数代表无效
    location.course;  // 航向
    location.speed; // 速度
    [location distanceFromLocation:[locations firstObject]]; // 用于计算两个位置的直线距离

    [self.localManager stopUpdatingLocation];
}

// 监听授权状态和定位服务的开启关闭
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    switch (status) {
        case kCLAuthorizationStatusNotDetermined:
            NSLog(@"没有决定(请求授权之前的状态)");
            break;
        case kCLAuthorizationStatusRestricted:
            NSLog(@"受限制");
            break;
        case kCLAuthorizationStatusDenied:          // 拒绝(永不、关闭定位服务)
            if([CLLocationManager locationServicesEnabled]){
                // 定位服务开启了
                NSLog(@"永不或者不允许Not Allow");
            } else {
                NSLog(@"定位服务关闭了");
                // 先允许定位,然后关闭掉定位服务,再开始定位startUpdatingLocation时系统会弹出设置定位服务打开弹框
            }
            break;
        case kCLAuthorizationStatusAuthorizedAlways://
            NSLog(@"始终:前后台");
            break;
        case kCLAuthorizationStatusAuthorizedWhenInUse://
            NSLog(@"使用应用期间:前台");
            break;
    }
}

- (CLLocationManager *)localManager {
    if (_localManager == nil) {
        _localManager = [[CLLocationManager alloc] init];
        _localManager.delegate = self;
        // 当下一个位置和上一个位置相差100米的时候才会调用代理方法didUpdateLocations
        _localManager.distanceFilter = 100;
        // 精确度:最适合导航、最好的、附近10米、附近100米、附近1000米、附近3000米
        _localManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
        if ([[UIDevice currentDevice].systemVersion floatValue] > 8.0) {
            [_localManager requestWhenInUseAuthorization];  // 请求前台授权
            if ([[UIDevice currentDevice].systemVersion floatValue] > 9.0) {
              _localManager.allowsBackgroundLocationUpdates = YES;
            }

//            [_localManager requestAlwaysAuthorization];   // 请求总是授权(前后台授权)
        }

    }

    return _localManager;
}
@end

通过开启或关闭定位服务,或者开启定位服务然后切换授权状态来监听授权状态的改变

iOS CoreLocation和MapKit详解_定位服务_09


iOS CoreLocation和MapKit详解_定位服务_07

2. 定位服务方式

  • 标准定位服务:
  • 是基于GPS/蓝牙/基站/wifi等来定位的,调用方法:startUpdatingLocation
  • 优点:定位精度高,
  • 缺点:程序关闭后就无法使用,而且比较耗电
  • 显著位置变化定位:
  • 是基于基站定位的,要求设备有电话模块,调用方法:startMonitoringSignificantLocationChanges
  • 优点:当App完全关闭是也可以进行定位;
  • 缺点:精度低

区域监听

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>

@interface ViewController () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *localManager;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 监听区域
    CLCircularRegion *circularRegion = [[CLCircularRegion alloc] initWithCenter:CLLocationCoordinate2DMake(37.785834, -122.406417) radius:1000 identifier:@"rangeMonitoring"];

     // 开始监听
    [self.localManager startMonitoringForRegion:circularRegion];

    [self.localManager requestStateForRegion:circularRegion];
}

// 进入区域范围
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(nonnull CLRegion *)region {
    NSLog(@"==didEnterRegion%@", region.identifier);
}

// 离开区域范围
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(nonnull CLRegion *)region {
    NSLog(@"==didExitRegion%@", region.identifier);
}

// requestStateForRegion 时调用
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(nonnull CLRegion *)region {
    if ([region.identifier isEqualToString:@"rangeMonitoring"]) {
        switch (state) {
            case CLRegionStateUnknown:
                NSLog(@"CLRegionStateUnknown");
                break;
            case CLRegionStateInside:
                NSLog(@"CLRegionStateInside");
                break;
            case CLRegionStateOutside:
                NSLog(@"CLRegionStateOutside");
                break;
        }
    }
}

- (CLLocationManager *)localManager {
    if (_localManager == nil) {
        _localManager = [[CLLocationManager alloc] init];
        _localManager.delegate = self;

        if ([[UIDevice currentDevice].systemVersion floatValue] > 8.0) {
            [_localManager requestAlwaysAuthorization];   // 请求总是授权(前后台授权)
        }

    }

    return _localManager;
}
@end

地理编码

地理编码: 将 某地址转换为经纬度
反地理编码:将经纬度转化为对应的地址

注意:反地理编码功能不靠谱,经常失败,还是用百度或者高德靠谱

#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>

@interface ViewController () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *localManager;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    // 地理编码
    [geocoder geocodeAddressString:@"北京天安门广场" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
        if (error == nil) {
            // 地标
            CLPlacemark *placemark = [placemarks firstObject];
            NSString *name = placemark.name;
            CLLocationDegrees latitude = placemark.location.coordinate.latitude;
            CLLocationDegrees longitude = placemark.location.coordinate.longitude;

            NSLog(@"name=%@, latitude=%f, longitude=%f", name, latitude, longitude);
        } else {
            NSLog(@"失败:error=%@", error);
        }

    }];
}

- (IBAction)test:(id)sender {
    [self.localManager startUpdatingLocation];
}

- (CLLocationManager *)localManager {
    if (_localManager == nil) {
        _localManager = [[CLLocationManager alloc] init];
        _localManager.delegate = self;

        if ([[UIDevice currentDevice].systemVersion floatValue] > 8.0) {
            [_localManager requestAlwaysAuthorization];
        }
    }

    return _localManager;
}



- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(nonnull NSArray<CLLocation *> *)locations {
    if (locations != nil) {
        [self.localManager stopUpdatingLocation];

        // 反地理编码(不靠谱)
        CLLocation * location = [locations firstObject];
        NSLog(@"location=%@", location);
        CLGeocoder *geocoder = [[CLGeocoder alloc] init];
        [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
            if (error == nil) {
                // 地标
                CLPlacemark *placemark = [placemarks firstObject];
                NSString *name = placemark.name;

                NSLog(@"name=%@", name);
            }else {
                NSLog(@"reverseGeocodeLocation失败:error=%@", error);
            }
        }];
    }
}
@end

第三方:GitHub: intuit/LocationManager block代码块方式


MapKit


  1. 拖入MapView
  2. Build Phases–> Link Binary With Libraries 中添加MapKit.framework 或者 不添加.framework 但是需要在ViewController中实例化MapView
#import "ViewController.h"
#import <MapKit/MapKit.h>
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [[MKMapView alloc] init];  // 在没有引入MapKit.framework情况下,如果不实例化会报错('Could not instantiate class named MKMapView'),但是如果引入了MapKit.framework 就不需要再实例化了
}
@end

MapView属性

type:

  • Standard(标准):显示地理及城市名字
  • Satellite(卫星):显示卫星云图和指南针
  • Hybrid(混合:标准+卫星)
  • SatelliteFlyover(3D立体卫星)
  • HybridFlyover(3D立体混合)

allows:

  • zooming(缩放) zoomEnabled
  • scrolling(滚动) scrollEnabled
  • rotating(旋转) rotateEnabled
  • 3D view

// 设置显示项
showsXxx
showsBuilding // 建筑
showsCompass // 指南针
showsPointsOfInterest // poi兴趣点
showsScale // 缩放比例
showsTraffic // 交通状况
showsUserLocation // 在地图上显示一个蓝色的点,显示当前用户的位置 只需要请求授权(需要配置Info.plist),不需要实现代理方法,需要设置locationManager
userTrackingMode // 用户追踪模式: 当前用户的位置会放大,当位置移动时,地图跟着移动 FollowWithHeading

#import "ViewController.h"
#import <MapKit/MapKit.h>

@interface ViewController () <MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (strong, nonatomic) CLLocationManager *locationManager;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self locationManager];

    _mapView.mapType = MKMapTypeStandard;

    _mapView.scrollEnabled = YES;
    _mapView.rotateEnabled = YES;
    _mapView.zoomEnabled = YES;

    _mapView.showsScale = YES;      // 显示比例尺
    _mapView.showsCompass = YES;    // 显示指南针
    _mapView.showsTraffic = YES;    // 显示路况
    _mapView.showsBuildings = YES;  // 显示建筑物
    _mapView.showsPointsOfInterest = YES;// 显示poi兴趣点
    _mapView.showsUserLocation = YES; // 显示用户位置
//    _mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
    _mapView.delegate = self;


}

- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
    // MKUserLocation: 大头针数据模型
    // MKAnnotation: 大头针

    userLocation.title = @"大头针标题";
    userLocation.subtitle = @"大头针子标题";

    // 设置地图的中心点所在的经纬度
//    [mapView setCenterCoordinate:userLocation.location.coordinate animated:YES];

    // 设置地图的区域
    MKCoordinateSpan coordinateSpan = MKCoordinateSpanMake(0.002, 0.001);
    MKCoordinateRegion coordinateRegion = MKCoordinateRegionMake(userLocation.location.coordinate, coordinateSpan);
    [mapView setRegion:coordinateRegion animated:YES];
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    NSLog(@"%f, %f", mapView.region.span.latitudeDelta, mapView.region.span.longitudeDelta);
}

- (CLLocationManager *)locationManager {
    if (_locationManager == nil) {
        _locationManager = [[CLLocationManager alloc] init];
        if ([[UIDevice currentDevice].systemVersion floatValue] > 8.0) {
            [_locationManager requestAlwaysAuthorization];
        }
    }

    return _locationManager;
}

@end

MKAnnotation大头针

在地图上操作大头针,实际上是操作的是大头针对应的数据模型,数据模型变了地图上的大头针跟随改变!在地图上添加或删除大头针实际上添加或删除大头针数据模型类

coordinate: 经纬度
title: 标题
subtitle: 子标题

#import "ViewController.h"
#import <MapKit/MapKit.h>

#import "Annotation.h"

@interface ViewController () <MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (strong, nonatomic) CLLocationManager *locationManager;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 在地图上操作大头针实际上是操作的大头针数据模型, 删除大头针就是删除大头针数据模型,添加大头针就是添加大头针数据模型

    // 获取触摸点所在父视图对应的坐标
    CGPoint point = [[touches anyObject] locationInView:self.mapView];
    // 根据坐标点获取对应的经纬度
    CLLocationCoordinate2D locationCoordinate2D = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
    CLLocation *location = [[CLLocation alloc] initWithLatitude:locationCoordinate2D.latitude longitude:locationCoordinate2D.longitude];

    Annotation *annotation = [[Annotation alloc] init];
    annotation.coordinate = locationCoordinate2D;
    annotation.title = @"大头针标题";
    annotation.subtitle = @"子标题";

    [self.mapView addAnnotation:annotation];

    [[[CLGeocoder alloc] init] reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
        if (error == nil) {
            NSLog(@"%@", placemarks);
            CLPlacemark * placemark = [placemarks firstObject];
            annotation.title = placemark.locality;
            annotation.subtitle = placemark.name;
        }
    }];
}


// 如果该方法没有实现或者return nil,系统则使用默认的大头针,如果要自定义大头针就返回自定义的
// 大头针和cell一样都有循环利用机制
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
    NSLog(@"------------");
    NSString *Identifier = @"Annotation";
    MKPinAnnotationView * annotationView= (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:Identifier];
    if (annotationView == nil) {
        annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:Identifier];
    }

    annotationView.canShowCallout = YES;  // 显示弹框
    annotationView.pinTintColor = [UIColor blackColor];  // 大头针颜色
    annotationView.animatesDrop = YES;
    UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 90)];
    redView.backgroundColor = [UIColor redColor];
    annotationView.leftCalloutAccessoryView = redView;

    UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 90)];
    greenView.backgroundColor = [UIColor greenColor];
    annotationView.rightCalloutAccessoryView = greenView;


    annotationView.detailCalloutAccessoryView = [[UISwitch alloc] init];


    return annotationView;
}
@end

iOS CoreLocation和MapKit详解_后台定位_11

导航

  • 方式一:使用系统功能 MKMapItem#openMapsWithItems:launchOptions需要两个参数
  • 起点地标和终点地标 MKPlacemark(CLPlacemark)
  • 定义地图的一些属性,如类型mapType
#import "ViewController.h"
#import <MapKit/MapKit.h>
@interface ViewController ()
@property (strong, nonatomic) CLGeocoder *geocoder;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.geocoder geocodeAddressString:@"上海" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

        CLPlacemark *startPlacemark = [placemarks firstObject];

        __weak typeof(self) weakSelf = self;
        [weakSelf.geocoder geocodeAddressString:@"北京" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

            CLPlacemark *endPlacemark = [placemarks firstObject];

            // mapItems
            MKPlacemark *startplacemark = [[MKPlacemark alloc] initWithPlacemark:startPlacemark];
            MKMapItem *startMapItem = [[MKMapItem alloc] initWithPlacemark:startplacemark];

            MKPlacemark *endplacemark = [[MKPlacemark alloc] initWithPlacemark:endPlacemark];
            MKMapItem *endMapItem = [[MKMapItem alloc] initWithPlacemark:endplacemark];
            NSArray<MKMapItem *> *mapItems = @[startMapItem, endMapItem];

            // launchOptions
            NSDictionary *launchOptions = @{MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeDriving,
                                            MKLaunchOptionsMapTypeKey: @(MKMapTypeStandard),
                                            MKLaunchOptionsShowsTrafficKey: @(YES)};

            [MKMapItem openMapsWithItems:mapItems launchOptions:launchOptions];

        }];

    }];
}

- (CLGeocoder *)geocoder {
    if (_geocoder == nil) {
        _geocoder = [[CLGeocoder alloc] init];
    }

    return _geocoder;
}
@end

iOS CoreLocation和MapKit详解_定位服务_12


地图截图 MKMapSnapshotter#startWithCompletionHandler

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    MKMapSnapshotOptions *mapSnapshotOptions = [[MKMapSnapshotOptions alloc] init];
    mapSnapshotOptions.mapType = MKMapTypeStandard;
    mapSnapshotOptions.region = self.mapView.region;
    mapSnapshotOptions.size = CGSizeMake(500, 500);
    MKMapSnapshotter *mapSnapshotter = [[MKMapSnapshotter alloc] initWithOptions:mapSnapshotOptions];
    [mapSnapshotter startWithCompletionHandler:^(MKMapSnapshot * _Nullable snapshot, NSError * _Nullable error) {
        if (nil == error) {
            NSData *data = UIImagePNGRepresentation(snapshot.image);
            [data writeToFile:@"/Users/macmini/Desktop/map.png" atomically:YES];
        } else {
            NSLog(@"error: %@",error);
        }
    }];
}

poi检索: 就是在指定的区域去搜索 美食、电影、酒店 等服务, 注意最多只有10条数据,数据条数有限制

MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
    request.naturalLanguageQuery = @"美食";
    request.region = self.mapView.region;
    MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
    [search startWithCompletionHandler:^(MKLocalSearchResponse * _Nullable response, NSError * _Nullable error) {
        if (error == nil) {
            for (MKMapItem *mapItem in response.mapItems) {
                NSLog(@"%@", mapItem.name);
            }

        }
    }];
// 结果:
2016-11-07 11:25:22.493 MKMapItem[680:22457] 阳天麻火腿鸡(总店)
2016-11-07 11:25:22.493 MKMapItem[680:22457] 六婆串串香(西昌店)
2016-11-07 11:25:22.494 MKMapItem[680:22457] 食全酒美(甘洛环城路)
2016-11-07 11:25:22.494 MKMapItem[680:22457] 比格比萨(西昌步行街餐厅)
2016-11-07 11:25:22.495 MKMapItem[680:22457] 肯德基(航天店)
2016-11-07 11:25:22.495 MKMapItem[680:22457] 大蓉和酒楼(西昌店)
2016-11-07 11:25:22.496 MKMapItem[680:22457] 小粥仙食屋(长安东路)
2016-11-07 11:25:22.497 MKMapItem[680:22457] 天天乐(人民路)
2016-11-07 11:25:22.498 MKMapItem[680:22457] 德克士(西昌店)
2016-11-07 11:25:22.498 MKMapItem[680:22457] 麦香园西饼屋(新街)

发送网络请求给苹果服务器获取导航数据 MKDirections#calculateDirectionsWithCompletionHandler

- (void)test {
    [self.geocoder geocodeAddressString:@"上海" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

        CLPlacemark *startPlacemark = [placemarks firstObject];

        __weak typeof(self) weakSelf = self;
        [weakSelf.geocoder geocodeAddressString:@"北京" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

            CLPlacemark *endPlacemark = [placemarks firstObject];

            // mapItems
            MKPlacemark *startplacemark = [[MKPlacemark alloc] initWithPlacemark:startPlacemark];
            MKMapItem *startMapItem = [[MKMapItem alloc] initWithPlacemark:startplacemark];

            MKPlacemark *endplacemark = [[MKPlacemark alloc] initWithPlacemark:endPlacemark];
            MKMapItem *endMapItem = [[MKMapItem alloc] initWithPlacemark:endplacemark];

            MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
            request.source = startMapItem;
            request.destination = endMapItem;

            MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
            [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
                if (error == nil) {
                    // MKRoute
                    // name: 线路名称
                    // advisoryNotices: 提示信息
                    // distance: 长度
                    // transportType:行走方式(步行、驾驶、公交)
                    // polyline:导航路线对应的数据模型
                    // steps:具体步骤
                    for (MKRoute *route in response.routes) {
                        NSLog(@"%@ %f %f", route.name, route.distance, route.expectedTravelTime);

                        for (MKRouteStep *step in route.steps) {
                            // instructions: 行走提示
                            // notice : 警告信息
                            // distance : 距离
                            // transportType: 交通方式
                            NSLog(@"%@", step.instructions);
                        }

                        NSLog(@"------------------------------------------------");
                    }
                }
            }];
        }];

    }];

//    2016-11-07 14:02:40.940 MKMapItem[879:138058] G15沈海高速 1224792.000000 49802.000000
//    2016-11-07 14:02:40.941 MKMapItem[879:138058] 路线导航开始
//    2016-11-07 14:02:40.941 MKMapItem[879:138058] 右转进入人民大道
//    2016-11-07 14:02:40.942 MKMapItem[879:138058] 右转,朝南京西路方向进入黄陂北路
//    2016-11-07 14:02:40.942 MKMapItem[879:138058] 左转进入威海路
//    2016-11-07 14:02:40.942 MKMapItem[879:138058] 在威海路靠右行驶
//    2016-11-07 14:02:40.943 MKMapItem[879:138058] 右转,朝南京西路方向进入成都北路
//    2016-11-07 14:02:40.943 MKMapItem[879:138058] 继续前行,朝共和新路立交方向进入南北高架路
//    2016-11-07 14:02:40.944 MKMapItem[879:138058] 靠左行驶并线进入南北高架路
//    2016-11-07 14:02:40.944 MKMapItem[879:138058] 靠左行驶并线进入南北高架路
//    2016-11-07 14:02:40.945 MKMapItem[879:138058] 驶出出口,并线进入中环路
//    2016-11-07 14:02:40.945 MKMapItem[879:138058] 朝机场、火车站方向靠左行驶
//    2016-11-07 14:02:40.945 MKMapItem[879:138058] 朝S5、嘉定城区方向驶出
//    2016-11-07 14:02:40.946 MKMapItem[879:138058] 朝G1501、安亭方向,靠左驶出
//    2016-11-07 14:02:40.946 MKMapItem[879:138058] 朝G15、宁波方向驶出
//    2016-11-07 14:02:40.947 MKMapItem[879:138058] 朝南通方向靠右行驶
//    2016-11-07 14:02:40.947 MKMapItem[879:138058] 朝G15、苏通大桥方向驶出
//    2016-11-07 14:02:40.948 MKMapItem[879:138058] 靠左行驶,朝G15、盐城方向并线进入G15沈海高速
//    2016-11-07 14:02:40.948 MKMapItem[879:138058] 靠左行驶,朝G15、赣榆方向并线进入G15沈海高速
//    2016-11-07 14:02:40.949 MKMapItem[879:138058] 朝G25、临沂方向驶出
//    2016-11-07 14:02:40.949 MKMapItem[879:138058] 朝临沂方向靠左行驶
//    2016-11-07 14:02:40.950 MKMapItem[879:138058] 朝北京、黄骅方向驶出
//    2016-11-07 14:02:40.950 MKMapItem[879:138058] 朝G18、G2方向驶出
//    2016-11-07 14:02:40.951 MKMapItem[879:138058] 朝G18、G2方向靠左行驶
//    2016-11-07 14:02:40.951 MKMapItem[879:138058] 朝保定、北京方向靠左行驶
//    2016-11-07 14:02:40.951 MKMapItem[879:138058] 靠右行驶并线进入永乐店服务区
//    2016-11-07 14:02:40.952 MKMapItem[879:138058] 朝东五环、G1方向驶出
//    2016-11-07 14:02:40.952 MKMapItem[879:138058] 朝东四环、G1方向驶出
//    2016-11-07 14:02:40.953 MKMapItem[879:138058] 在双龙路靠左行驶
//    2016-11-07 14:02:40.953 MKMapItem[879:138058] 右转,朝东二环、光明桥方向进入广渠门南滨河路
//    2016-11-07 14:02:40.954 MKMapItem[879:138058] 靠左行驶并线进入东二环
//    2016-11-07 14:02:40.954 MKMapItem[879:138058] 稍向右转,朝磁器口方向进入广渠门内大街
//    2016-11-07 14:02:40.954 MKMapItem[879:138058] 在崇文门外大街,朝崇文门方向靠右行驶
//    2016-11-07 14:02:40.955 MKMapItem[879:138058] 在崇文门外大街靠右行驶
//    2016-11-07 14:02:40.956 MKMapItem[879:138058] 左转,朝前门方向进入崇文门西大街
//    2016-11-07 14:02:40.957 MKMapItem[879:138058] 右转进入正义路
//    2016-11-07 14:02:40.957 MKMapItem[879:138058] 调头
//    2016-11-07 14:02:40.957 MKMapItem[879:138058] 请准备停车
//    2016-11-07 14:02:40.957 MKMapItem[879:138058] 右转
//    2016-11-07 14:02:40.958 MKMapItem[879:138058] 右转
//    2016-11-07 14:02:40.958 MKMapItem[879:138058] 左转
//    2016-11-07 14:02:40.958 MKMapItem[879:138058] 目的地在您左侧
}

给地图添加覆盖层如折现、圆形

覆盖层的实现和UITableViewCell的实现差不多,首先要有数据,有了数据还要对应的UI部分

  1. 创建覆盖层数据模型并添加到MapView上(系统执行addOverlay后会调用对应的代理方法)
  2. 代理方法中返回对应的覆盖层渲染器:MKOverlayRenderer

注意如果在mapView中添加多种类型的覆盖层,需要在代理方法中进行判断,因为只要执行addOverlay就会执行代理方法,如果执行的addOverlay和代理返回的覆盖层渲染器不匹配会报错

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 1. 创建一个圆形的覆盖层数据模型
//    MKCircle *circle = [MKCircle circleWithCenterCoordinate:self.mapView.centerCoordinate radius:500000];
//    // 2. 添加覆盖层模型数据到地图上
//    [self.mapView addOverlay:circle];

    // 3. 系统会自动调用代理方法mapView:rendererForOverlay
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    // 不同的覆盖层数据模型对应不同的覆盖层视图
//    MKCircleRenderer *circleRenderer = [[MKCircleRenderer alloc] initWithOverlay:overlay];
//    circleRenderer.fillColor = [UIColor cyanColor];
//    circleRenderer.alpha = 0.5;
//    return circleRenderer;

    MKPolylineRenderer *polylineRenderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
    polylineRenderer.lineWidth = 6;
    polylineRenderer.strokeColor = [UIColor redColor];

    return polylineRenderer;

}

#import "ViewController.h"
#import <MapKit/MapKit.h>
@interface ViewController ()<MKMapViewDelegate>
@property (strong, nonatomic) CLGeocoder *geocoder;
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.geocoder geocodeAddressString:@"上海" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

        CLPlacemark *startPlacemark = [placemarks firstObject];

        MKCircle *startCircle = [MKCircle circleWithCenterCoordinate:startPlacemark.location.coordinate radius:200000];
        [self.mapView addOverlay:startCircle];

        __weak typeof(self) weakSelf = self;
        [weakSelf.geocoder geocodeAddressString:@"北京" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

            CLPlacemark *endPlacemark = [placemarks firstObject];
            MKCircle *endCircle = [MKCircle circleWithCenterCoordinate:endPlacemark.location.coordinate radius:200000];
            [self.mapView addOverlay:endCircle];


            // mapItems
            MKPlacemark *startplacemark = [[MKPlacemark alloc] initWithPlacemark:startPlacemark];
            MKMapItem *startMapItem = [[MKMapItem alloc] initWithPlacemark:startplacemark];

            MKPlacemark *endplacemark = [[MKPlacemark alloc] initWithPlacemark:endPlacemark];
            MKMapItem *endMapItem = [[MKMapItem alloc] initWithPlacemark:endplacemark];

            MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
            request.source = startMapItem;
            request.destination = endMapItem;

            MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
            [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
                if (error == nil) {
                    // MKRoute
                    // name: 线路名称
                    // advisoryNotices: 提示信息
                    // distance: 长度
                    // transportType:行走方式(步行、驾驶、公交)
                    // polyline:导航路线对应的数据模型
                    // steps:具体步骤
                    for (MKRoute *route in response.routes) {
                        NSLog(@"%@ %f %f", route.name, route.distance, route.expectedTravelTime);
                        [self.mapView addOverlay:route.polyline];

                        for (MKRouteStep *step in route.steps) {

                            // instructions: 行走提示
                            // notice : 警告信息
                            // distance : 距离
                            // transportType: 交通方式
                            NSLog(@"%@", step.instructions);
                        }

                        NSLog(@"------------------------------------------------");
                    }
                }
            }];
        }];

    }];

}

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    if ([overlay isKindOfClass:[MKPolyline class]]) {
        MKPolylineRenderer *polylineRenderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
        polylineRenderer.lineWidth = 6;
        polylineRenderer.strokeColor = [UIColor redColor];

        return polylineRenderer;
    } else if ([overlay isKindOfClass:[MKCircle class]]) {
        // 不同的覆盖层数据模型对应不同的覆盖层视图
        MKCircleRenderer *circleRenderer = [[MKCircleRenderer alloc] initWithOverlay:overlay];
        circleRenderer.fillColor = [UIColor cyanColor];
        circleRenderer.alpha = 0.5;
        return circleRenderer;
    }

    return nil;


}


- (CLGeocoder *)geocoder {
    if (_geocoder == nil) {
        _geocoder = [[CLGeocoder alloc] init];
    }

    return _geocoder;
}

@end

运行结果:

iOS CoreLocation和MapKit详解_定位服务_13