【iOS】7.4 定位服务->3.1 地图框架MapKit 功能1:地图展示

本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。


本文相关目录:
================== 所属文集:【iOS】07 设备工具 ==================
7.4 定位服务->1.0 简介
7.4 定位服务->2.1.1 定位 - 官方框架CoreLocation: 请求用户授权
7.4 定位服务->2.1.2 定位 - 官方框架CoreLocation: CLLocationManager位置管理器
7.4 定位服务->2.1.3.1 定位 - 官方框架CoreLocation 功能1:地理定位
7.4 定位服务->2.1.3.2 定位 - 官方框架CoreLocation 功能2:地理编码和反地理编码
7.4 定位服务->2.1.3.3 定位 - 官方框架CoreLocation 功能3:区域监听
7.4 定位服务->2.1.4 定位 - 官方框架CoreLocation 案例:指南针效果
7.4 定位服务->2.2 定位 - locationManager框架
7.4 定位服务->3.1 地图框架MapKit 功能1:地图展示
7.4 定位服务->3.2 地图框架MapKit 功能2:路线规划(导航)
7.4 定位服务->3.3 地图框架MapKit 功能3:3D视图
7.4 定位服务->3.4 地图框架MapKit 功能4:地图截图
7.4 定位服务->3.5 地图框架MapKit 功能5:POI检索
================== 所属文集:【iOS】07 设备工具 ==================


地图框架 - MapKit目录:

本文目录:


1.0 地图简介

下面展示上图中的3种效果图:
大头针效果

路线效果

覆盖层效果


2.0 使用步骤


3.0 地图的基本使用

3.1 地图显示类型

下面介绍下地图显示类型的分类:
标准地图

卫星云图

混合模式(标准地图覆盖于卫星云图之上 )

3D立体卫星 (iOS9.0)

3D立体混合 (iOS9.0)

3.2 地图控制项

3.3 地图显示项

3.4 地图显示用户位置

3.5 常见问题

3.6 测试环境

代码11:地图的基本使用 Demo

编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0

配置 info.plist 文件

【OC 语言】

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

@interface ViewController ()
@property(weak, nonatomic) IBOutlet MKMapView *mapView;    // 地图view
@property(nonatomic, strong) CLLocationManager *locationM; // 位置管理器
@end

@implementation ViewController

#pragma mark - 懒加载
- (CLLocationManager *)locationM {
  if (!_locationM) {
    // 创建位置管理者
    _locationM = [[CLLocationManager alloc] init];

    // 请求用户授权
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
      //    if ([_locationM
      //    respondsToSelector:@selector(requestAlwaysAuthorization)]) {
      [_locationM requestAlwaysAuthorization];
    }
  }
  return _locationM;
}

- (void)viewDidLoad {
  [super viewDidLoad];
#pragma mark - 设置地图显示类型
  self.mapView.mapType = MKMapTypeStandard;

#pragma mark - 设置地图的控制项
  // 注意:设置对应的属性时,注意该属性是从哪个系统版本开始引入的,做好不同系统版本的适配
  self.mapView.zoomEnabled = true;   // 是否可以缩放
  self.mapView.scrollEnabled = true; // 是否可以滚动
  self.mapView.rotateEnabled = true; // 是否可以旋转
  self.mapView.pitchEnabled = true;  // 是否显示3D

#pragma mark - 设置地图显示项
  self.mapView.showsCompass = true;          // 是否显示指南针
  self.mapView.showsScale = true;            // 是否显示比例尺
  self.mapView.showsPointsOfInterest = true; // 是否显示兴趣点(POI)
  self.mapView.showsBuildings = true;        // 是否显示建筑物
  self.mapView.showsTraffic = true;          // 是否显示交通

#pragma mark - 地图显示用户位置

  [self locationM]; //调用懒加载,请求用户授权

  // 显示用户位置方案1:(需要请求用户授权)
  // 效果:显示一个蓝点,在地图上面标示用户的位置信息
  // 缺点:不会自动放大地图,当用户位置移动时,地图不会自动跟着跑
  self.mapView.showsUserLocation = true;

  // 显示用户位置方案2:设置地图的跟随模式(需要请求用户授权)
  // 效果:显示一个蓝点,在地图上面标示用户的位置信息,会自动放大地图,当用户位置移动时,地图会自动跟着跑
  // 缺点:拖动地图后,地图不会再随着用户位置移动而移动
  /*
   MKUserTrackingModeNone = 0, // 不跟随
   MKUserTrackingModeFollow, // 跟随用户位置
   MKUserTrackingModeFollowWithHeading, // 跟随用户位置,并跟随用户方向
   */
  self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end
 
【Swift 语言】
import UIKit
import MapKit

class ViewController: UIViewController {
    
    @IBOutlet weak var mapView: MKMapView! // 地图view
    
    // MARK: - 懒加载
    lazy var locationM: CLLocationManager = {
        
        let locationM = CLLocationManager()
        
        // 请求用户授权(判断设备的系统)(当前target为7.0)
        if #available(iOS 8.0, *) {
            locationM.requestAlwaysAuthorization()
        }
        return locationM
    }()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // MARK: - 设置地图显示类型
        mapView.mapType = MKMapType.standard
        
        // MARK: - 设置地图的控制项
        // 注意:设置对应的属性时,注意该属性是从哪个系统版本开始引入的,做好不同系统版本的适配
        mapView.isZoomEnabled = true  // 是否可以缩放
        mapView.isScrollEnabled = true // 是否可以滚动
        mapView.isRotateEnabled = true // 是否可以旋转
        mapView.isPitchEnabled = true // 是否显示3D
        
        
        // MARK: - 设置地图显示项
        mapView.showsPointsOfInterest = true // 是否显示兴趣点(POI)
        mapView.showsBuildings = true // 是否显示建筑物
        
        if #available(iOS 9.0, *) {// 下面的方法是iOS 9.0以后加入的
            mapView.showsCompass = true  // 是否显示指南针
            mapView.showsScale = true // 是否显示比例尺
            mapView.showsTraffic = true // 是否显示交通
        }
        
        
        // MARK: - 地图显示用户位置
        _ = locationM //调用懒加载,请求用户授权
        
        // 显示用户位置方案1:(需要请求用户授权)
        // 效果:显示一个蓝点,在地图上面标示用户的位置信息
        // 缺点:不会自动放大地图,当用户位置移动时,地图不会自动跟着跑
        mapView.showsUserLocation = true
        
        // 显示用户位置方案2:设置地图的跟随模式(需要请求用户授权)
        // 效果:显示一个蓝点,在地图上面标示用户的位置信息,会自动放大地图,当用户位置移动时,地图会自动跟着跑
        // 缺点:拖动地图后,地图不会再随着用户位置移动而移动
        /*
         case none   // 不跟随
         case follow // 跟随用户位置
         case followWithHeading // 跟随用户位置,并跟随用户方向
         */
        mapView.userTrackingMode = MKUserTrackingMode.followWithHeading
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

4.0 功能实现:模拟追踪显示用户位置

4.1 代码实现

步骤1:设置地图代理(略)

步骤2:实现代理方法

步骤3:MKUserLocation 大头针(数据)模型

代码13:大头针基本使用 Demo

编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0

【OC 语言】

TDAnnotation.h

#pragma mark - 自定义大头针模型
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface TDAnnotation : NSObject<MKAnnotation>
// 大头针所在经纬度(订在地图哪个位置)
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
// 大头针标注显示的标题
@property (nonatomic, copy, nullable) NSString *title;
// 大头针标注显示的子标题
@property (nonatomic, copy, nullable) NSString *subtitle;
@end

ViewController.m

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

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

@implementation ViewController

#pragma mark - 懒加载
-(CLLocationManager *)locationM{
    if (!_locationM) {
        _locationM = [[CLLocationManager alloc] init];
        if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
            [_locationM requestAlwaysAuthorization];
        }
    }
    return _locationM;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self locationM];
    
    // 设置用户位置跟踪模式
    self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
}

#pragma mark - 添加大头针
- (void)addAnnotationWithCoordinate:(CLLocationCoordinate2D)coordinate{
    // 1、创建一个大头针数据模型
    TDAnnotation *annotation = [[TDAnnotation alloc] init];
    
    // 2、大头针数据模型的属性
    annotation.coordinate = coordinate; //根据经纬度坐标添加大头针
    //    annotation.coordinate = self.customMapView.centerCoordinate;// 当前地图中心点对应的经纬度
    
    annotation.title = @"大头针弹框的标题";
    annotation.subtitle = @"大头针弹框的子标题";
    
    // 3、添加大头针数据模型, 到地图上
    [self.mapView addAnnotation:annotation];
}

#pragma mark - 移除地图上所有大头针
- (void)removeAllAnnotation{
    // 1. 获取所有的大头针数据模型
    NSArray *annotations = self.mapView.annotations;
    // 2. 移除大头针
    [self.mapView removeAnnotations:annotations];
}

#pragma mark - 触摸屏幕
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 获取当前触摸点在地图上的坐标
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self.mapView];
    
    // 将坐标转换为经纬度
    CLLocationCoordinate2D center = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
    
    [self addAnnotationWithCoordinate:center];
}

#pragma mark - 触摸移动时,移除地图上所有大头针
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self removeAllAnnotation];
}

#pragma mark - MKMapViewDelegate
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
    userLocation.title = @"用户位置的标题";
    userLocation.subtitle = @"用户位置的标题";
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

运行效果:

【Swift 语言】

import UIKit
import MapKit

class ViewController: UIViewController {

    @IBOutlet weak var mapView: MKMapView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 1、创建一个大头针数据模型
        let annotation: TDAnnotation = TDAnnotation()
        
        // 2、大头针数据模型的属性
        annotation.coordinate = mapView.centerCoordinate
        annotation.title = "大头针弹框的标题"
        annotation.subtitle = "大头针弹框的子标题"
        
        // 3、添加大头针数据模型, 到地图上
        mapView.addAnnotation(annotation)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 1. 获取所有的大头针数据模型
        let annotations = mapView.annotations
        
        // 2. 移除大头针
        mapView.removeAnnotations(annotations)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

extension ViewController: MKMapViewDelegate {
    
    func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
        userLocation.title = "用户位置的标题"
        userLocation.subtitle = "用户位置的标题"
    }
}

代码14:大头针场景模拟 Demo

编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0

【OC 语言】

TDAnnotation.h

#pragma mark - 自定义大头针模型
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface TDAnnotation : NSObject<MKAnnotation>
// 大头针所在经纬度(订在地图哪个位置)
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
// 大头针标注显示的标题
@property (nonatomic, copy, nullable) NSString *title;
// 大头针标注显示的子标题
@property (nonatomic, copy, nullable) NSString *subtitle;
@end

ViewController.m


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

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

@implementation ViewController

#pragma mark - 懒加载
-(CLLocationManager *)locationM{
    if (!_locationM) {
        _locationM = [[CLLocationManager alloc] init];
        if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
            [_locationM requestAlwaysAuthorization];
        }
    }
    return _locationM;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self locationM];
    
    // 设置用户位置跟踪模式
    self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
}

#pragma mark - 添加大头针
- (void)addAnnotationWithCoordinate:(CLLocationCoordinate2D)coordinate{
    // 1、创建一个大头针数据模型
    TDAnnotation *annotation = [[TDAnnotation alloc] init];
    
    // 2、大头针数据模型的属性
    annotation.coordinate = coordinate; //根据经纬度坐标添加大头针
    //  annotation.coordinate = self.customMapView.centerCoordinate;// 当前地图中心点对应的经纬度
    
    annotation.title = @"大头针弹框的标题";
    annotation.subtitle = @"大头针弹框的子标题";
    
    // 3、添加大头针数据模型, 到地图上
    [self.mapView addAnnotation:annotation];
}

#pragma mark - 移除地图上所有大头针
- (void)removeAllAnnotation{
    // 1. 获取所有的大头针数据模型
    NSArray *annotations = self.mapView.annotations;
    // 2. 移除大头针
    [self.mapView removeAnnotations:annotations];
}

#pragma mark - 触摸屏幕
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 获取当前触摸点在地图上的坐标
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self.mapView];
    
    // 将坐标转换为经纬度
    CLLocationCoordinate2D center = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
    
    [self addAnnotationWithCoordinate:center];
}

#pragma mark - 触摸移动时,移除地图上所有大头针
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self removeAllAnnotation];
}

#pragma mark - MKMapViewDelegate
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
    userLocation.title = @"用户位置的标题";
    userLocation.subtitle = @"用户位置的标题";
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
【Swift 语言】

TDAnnotation.swift


// 继承自NSObject,遵守MKAnnotation协议
import MapKit

class TDAnnotation: NSObject,MKAnnotation {
    
    // 确定大头针扎在地图上哪个位置
    var coordinate: CLLocationCoordinate2D
    
    // 确定大头针弹框的标题
    var title: String?
    
    // 确定大头针弹框的子标题
    var subtitle: String?
    
    // 构造方法
    init(coordinate:CLLocationCoordinate2D!, title:String?, subtitle:String?){
        self.coordinate = coordinate
        self.title = title
        self.subtitle = subtitle
    }
}

ViewController.swift


// 场景描述:鼠标点击在地图哪个位置, 就在对应的位置添加一个大头针, 并在标注弹框中显示对应的城市和街道;
import UIKit
import MapKit

class ViewController: UIViewController {

    @IBOutlet weak var mapView: MKMapView!
    
    //MARK: - 地理编码懒加载
    lazy var geoCoder : CLGeocoder = CLGeocoder()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    //MARK: - 点击屏幕
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){
        // 1. 获取当前触摸点所在的位置
        let point = touches.first?.location(in: mapView)
        
        // 2. 将从mapView上获取的点转换对应的经纬度坐标
        let coordinate = mapView.convert(point!, toCoordinateFrom: mapView)
        
        // 3.1 根据经纬度创建大头针数据模型
        let annotation = TDAnnotation(coordinate:coordinate, title: "大头针弹框的标题", subtitle: "大头针弹框的子标题")
        
        // 4. 进行反地理编码
        let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
        
        geoCoder.reverseGeocodeLocation(location) { (pls:[CLPlacemark]?, error:Error?) in
            
            if error == nil {
                let pl = pls?.first // 这里取第一个
                print(pl)
                
                // 赋值
                annotation.title = pl?.locality
                annotation.subtitle = pl?.name
            }
        }
        // 3.2 添加大头针数据模型到地图
        mapView.addAnnotation(annotation)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}


相关类的属性:
MKAnnotationView(大头针控件)

MKPinAnnotationView(大头针系统对应的视图)

模拟系统默认的大头针实现方案

代理方法补充

代码15:自定义大头针 Demo

编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0

【OC 语言】

TDAnnotation.h

#pragma mark - 自定义大头针模型
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface TDAnnotation : NSObject<MKAnnotation>
// 大头针所在经纬度(订在地图哪个位置)
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
// 大头针标注显示的标题
@property (nonatomic, copy, nullable) NSString *title;
// 大头针标注显示的子标题
@property (nonatomic, copy, nullable) NSString *subtitle;
//自定义大头针图片
@property(nonatomic,copy, nullable) NSString *icon;
@end

ViewController.m

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

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

@implementation ViewController

#pragma mark - 懒加载
-(CLLocationManager *)locationM{
    if (!_locationM) {
        _locationM = [[CLLocationManager alloc] init];
        if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
            [_locationM requestAlwaysAuthorization];
        }
    }
    return _locationM;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self locationM];
    
    // 设置用户位置跟踪模式
    self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
}

#pragma mark - 添加大头针
- (void)addAnnotationWithCoordinate:(CLLocationCoordinate2D)coordinate{
    // 1、创建一个大头针数据模型
    TDAnnotation *annotation = [[TDAnnotation alloc] init];
    
    // 2、大头针数据模型的属性
    annotation.coordinate = coordinate; //根据经纬度坐标添加大头针
    //  annotation.coordinate = self.customMapView.centerCoordinate;// 当前地图中心点对应的经纬度
    annotation.title = @"大头针弹框的标题";
    annotation.subtitle = @"大头针弹框的子标题";
    annotation.icon = @"1";

    // 设置代理
    self.mapView.delegate = self;
    
     // 反地理编码:获取当前经纬度所在位置信息,用作大头针标注的标题和子标题
      CLGeocoder *geocoder = [[CLGeocoder alloc] init];
      CLLocation *location =[[CLLocation alloc] initWithLatitude:coordinate.latitude
                                                    longitude:coordinate.longitude];
    
      [geocoder reverseGeocodeLocation:location
                     completionHandler:^(NSArray<CLPlacemark *> *_Nullable placemarks, NSError *_Nullable error) {
                   
                 if (error || placemarks.count == 0) {
                   return;
                 }
                         
                 //获取地标信息
                 CLPlacemark *placemark = [placemarks firstObject];
                 annotation.title = placemark.locality;
                 annotation.subtitle = placemark.name;
        }];
    
    // 3、添加大头针数据模型, 到地图上
    [self.mapView addAnnotation:annotation];
}

#pragma mark - 移除地图上所有大头针
- (void)removeAllAnnotation{
    // 1. 获取所有的大头针数据模型
    NSArray *annotations = self.mapView.annotations;
    // 2. 移除大头针
    [self.mapView removeAnnotations:annotations];
}

#pragma mark - 触摸屏幕
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 获取当前触摸点在地图上的坐标
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self.mapView];
    
    // 将坐标转换为经纬度
    CLLocationCoordinate2D center = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
    
    [self addAnnotationWithCoordinate:center];
}

#pragma mark - 触摸移动时,移除地图上所有大头针
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self removeAllAnnotation];
}

#pragma mark - MKMapViewDelegate
// 根据传进来的 viewForAnnotation 参数创建并返回对应的大头针控件
// 当添加大头针数据模型时,会调用此方法,获取对应的大头针视图。如果返回nil,则会显示系统默认的大头针视图。
// 系统默认的大头针视图对应的类 MKPinAnnotationView,大头针视图与tableview中的cell一样, 都使用“循环利用”的机制
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(TDAnnotation *)annotation{
    NSLog(@"添加大头针数据模型时, 调用了这个方法");
    
    // 判断annotation的类型,如果返回为空,代表大头针样式是由系统管理的 (即为光标样式)
    if ([annotation isKindOfClass:[MKUserLocation class]]) {
        NSLog(@"大头针样式是由系统管理的 (即为光标样式)");
        return nil;
    }
    
#pragma mark - 默认运行会显示案例1效果,注释案例1可以看到案例2的效果

//  ================= 自定义方法案例1(模拟系统默认的大头针视图)
    
    // 0. 从缓存池取出大头针视图
    static NSString *ID1 = @"PinAnnotationView";
    
    // 1. MKPinAnnotationView 有界面,默认不能显示图片
    // 将 MKAnnotationView 类型转换为 MKPinAnnotationView
    MKPinAnnotationView *PinAnnotationView = (MKPinAnnotationView *) [mapView  dequeueReusableAnnotationViewWithIdentifier:ID1];
    
    // 2. 如果为空,则创建
    if (!PinAnnotationView) {
        PinAnnotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation
                                                         reuseIdentifier:ID1];
    }
    
    // 3. 传递模型数据,重新赋值, 防止循环利用时, 产生的数据错乱
    PinAnnotationView.annotation = annotation;
    
    // 4. 设置大头针可以弹框
    PinAnnotationView.canShowCallout = YES;
    
    // 5. 设置大头针的颜色
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
        // iOS9.0以后, 可以设置任意颜色 (MKAnnotationView没有此方法)
        PinAnnotationView.pinTintColor = [UIColor blueColor] ;
    }else{
        // iOS 8.0的方法,只有3种颜色(MKAnnotationView没有此方法)
        PinAnnotationView.pinColor = MKPinAnnotationColorPurple;
    }
    
    // 6. 设置大头针下落动画(MKAnnotationView没有此方法)
    PinAnnotationView.animatesDrop = YES;

    // 7. 设置大头针可以被拖拽(父类中的属性)
    PinAnnotationView.draggable = YES;

    return PinAnnotationView;
    
//  ================= 自定义方法案例1(模拟系统默认的大头针视图)
    

//  ================= 自定义方法案例2(自定义大头针)
  
    // 0. 从缓存池取出大头针视图
    static NSString *ID2 = @"annotationView";
    
    // 1. MKAnnotationView 默认没有界面,可以显示图片
    MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:ID2];
    
    // 2. 如果为空,则创建
    if (!annotationView) {
        annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
                                                         reuseIdentifier:ID2];
    }
    
    // 4. 设置大头针图片
    // 方法1:直接使用 MKAnnotationView 的 image 属性
    annotationView.image = [UIImage imageNamed:@"2"];
    
    // 方法2:使用自定义大头针属性
    // annotationView.image = [UIImage imageNamed:annotation.icon];

    // 5. 设置大头针可以弹出标注
    annotationView.canShowCallout = YES;
    
    // 5.1 设置标注左侧视图
    UIImageView *leftIV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
    leftIV.image = [UIImage imageNamed:@"huba.jpeg"];
    annotationView.leftCalloutAccessoryView = leftIV;
    
    // 5.2 设置标注右侧视图
    UIImageView *rightIV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
    rightIV.image = [UIImage imageNamed:@"htl.jpg"];
    annotationView.rightCalloutAccessoryView = rightIV;
    
    // 6. 设置下部弹框(详情视图),会把子标题覆盖
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
        annotationView.detailCalloutAccessoryView = [[UISwitch alloc] init];
    }
    
    return annotationView;
    
//  ================= 自定义方法案例2(自定义大头针)
    
}

// 当大头针马上添加到 mapView 时调用
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray<MKAnnotationView *> *)views {
    
    NSLog(@"下面将改变大头针下落的速度");
    
    for (MKAnnotationView *view in views) {
        
        // if 条件的使用是为了不让光标类型的大头针有动画效果
        // MKModernUserLocationView 是一个私有类; NSClassFromString 把字符串转换成一个 class 类型
        if ([view isKindOfClass:NSClassFromString(@"MKModernUserLocationView")]) {
            continue;
        }
        
        // 1.保存大头针的最终位置
        CGRect viewFrame = view.frame;
        
        // 2.改变大头针的位置
        view.frame = CGRectMake(viewFrame.origin.x, 0, viewFrame.size.width,viewFrame.size.height);
        
        // 3.动画回归最终的位置
        [UIView animateWithDuration:0.25 animations:^{
                view.frame = viewFrame;
        }];
    }
}

-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
    userLocation.title = @"当前位置的标题";
    userLocation.subtitle = @"当前位置的子标题";
}

// 选中一个大头针时调用
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
    NSLog(@"选中%@", [view.annotation title]);
}

// 取消选中大头针时调用
-(void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{
    NSLog(@"取消选中%@", [view.annotation title]);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

运行效果在 swift 版本的代码后

【Swift 语言】

TDAnnotation.swift

// 继承自NSObject,遵守MKAnnotation协议
import MapKit

class TDAnnotation: NSObject,MKAnnotation {
    
    // 确定大头针扎在地图上哪个位置
    var coordinate: CLLocationCoordinate2D
    
    // 确定大头针弹框的标题
    var title: String?
    
    // 确定大头针弹框的子标题
    var subtitle: String?
    
    // 构造方法
    init(coordinate:CLLocationCoordinate2D!, title:String?, subtitle:String?){
        self.coordinate = coordinate
        self.title = title
        self.subtitle = subtitle
    }
}

ViewController.swift


import UIKit
import MapKit

class ViewController: UIViewController {
    
    // MARK: - 地图懒加载
    @IBOutlet weak var mapView: MKMapView!
    
    // MARK: - 地理编码懒加载
    lazy var geoCoder : CLGeocoder = CLGeocoder()
    
    // MARK: - viewDidLoad
    override func viewDidLoad() {
        
        super.viewDidLoad()
        mapView.delegate = self
    }
    
    //MARK: - 触摸屏幕,添加大头针数据模型
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 1. 获取当前触摸点所在的位置
        let point = touches.first?.location(in: mapView)
        
        // 2. 把触摸点所在的位置, 转换成为在地图上的经纬度坐标
        let coordinate =  mapView.convert(point!, toCoordinateFrom: mapView)
        
        // 3.1 创建大头针数据模型
        
        // 方法1:使用系统大头针数据模型
        // let annotation = MKUserLocation()
        // 这个赋值操作会报错, 因为该属性是只读属性, 所以, 系统提供的大头针数据模型, 我们没法使用,只能自定义大头针数据模型
        // annotation.location = CLLocation(latitude: mapView.centerCoordinate.latitude, longitude: mapView.centerCoordinate.longitude)
        
        // 方法2:使用自定义大头针数据模型
        let annotation = TDAnnotation(coordinate: coordinate, title: "大头针弹框的标题", subtitle: "大头针弹框的子标题")
        
        // 4. 反地理编码
        let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
        
        geoCoder.reverseGeocodeLocation(location) { (pls:[CLPlacemark]?, error:Error?) in
            if error  == nil
            {
                let pl = pls!.first
                
                annotation.title = pl?.locality
                annotation.subtitle = pl?.name
            }
        }
        // 3.2 添加大头针数据模型到地图
        mapView.addAnnotation(annotation)
    }
    
    //MARK: - 移除大头针数据模型
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("移除大头针数据模型时, 调用了这个方法")
        mapView.removeAnnotations(mapView.annotations)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

// 代理方法
extension ViewController: MKMapViewDelegate {
    // 根据传进来的 annotation 参数创建并返回对应的大头针控件
    // 当添加大头针数据模型时,会调用此方法,获取对应的大头针视图。如果返回nil,则会显示系统默认的大头针视图。
    // 系统默认的大头针视图对应的类 MKPinAnnotationView,大头针视图与tableview中的cell一样, 都使用“循环利用”的机制
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        print("添加大头针数据模型时, 调用了这个方法")
        
//MARK: - 默认运行会显示案例1效果,注释案例1可以看到案例2的效果
        
//  ================= 自定义方法案例1(模拟系统默认的大头针视图)
        
        // 0. 从缓存池取出大头针视图
        let ID1 = "PinAnnotationView"
        
        // 1. MKPinAnnotationView 有界面,默认不能显示图片
        // 将 MKAnnotationView 类型转换为 MKPinAnnotationView
        var PinAnnotationView:MKPinAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: ID1) as! MKPinAnnotationView?
        
        // 2. 如果为空,则创建
        if PinAnnotationView == nil{
            PinAnnotationView = MKPinAnnotationView(annotation: annotation,
                                                    reuseIdentifier: ID1)
        }
        
        // 3. 传递模型数据,重新赋值, 防止循环利用时, 产生的数据错乱
        PinAnnotationView?.annotation = annotation
        
        // 4. 设置大头针可以弹框
        PinAnnotationView?.canShowCallout = true
        
        // 5. 设置大头针的颜色
        if #available(iOS 9.0, *) {
            // iOS9.0以后, 可以设置任意颜色(MKAnnotationView没有此方法)
            PinAnnotationView?.pinTintColor = UIColor.black
        } else {
            // iOS 8.0的方法,只有3种颜色(MKAnnotationView没有此方法)
            PinAnnotationView?.pinColor = MKPinAnnotationColor.green
        }
        
        // 6. 设置大头针下落动画(MKAnnotationView没有此方法)
        PinAnnotationView?.animatesDrop = true
        
        return PinAnnotationView
        
//  ================= 自定义方法案例1(模拟系统默认的大头针视图)
        
        
//  ================= 自定义方法案例2(自定义大头针)
        
        // 1. 从缓存池取出大头针视图
        let ID2 = "annotationView"
        // MKAnnotationView 默认没有界面,可以显示图片
        var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: ID2)
        
        // 2. 如果为空,则创建
        if annotationView == nil{
            annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: ID2)
        }
        
        // 3. 重新赋值, 防止循环利用时, 产生的数据错乱
        annotationView!.annotation = annotation
        
        // 4. 设置大头针图片
        annotationView!.image = UIImage(named: "1")
        
        // 5. 设置大头针中心偏移量
        annotationView?.centerOffset = CGPoint(x: 0, y: 0)
        
        // 6. 设置可以弹框
        annotationView!.canShowCallout = true
        // 设置弹框的偏移量
        annotationView?.calloutOffset = CGPoint(x: -10, y: 10)
        
        // 6.1 设置弹框左侧视图
        let leftIMV = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
        leftIMV.image = UIImage(named: "huba")
        annotationView?.leftCalloutAccessoryView = leftIMV
        
        // 6.2 设置弹框右侧视图
        let rightIMV = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
        rightIMV.image = UIImage(named: "htl")
        annotationView?.rightCalloutAccessoryView = rightIMV
        
        // 6.3 设置下部弹框(详情视图),会把子标题覆盖
        if #available(iOS 9.0, *) {
            annotationView?.detailCalloutAccessoryView = UISwitch()
        }
        
        // 7. 设置大头针可以拖动
        annotationView?.isDraggable = true
        
        return annotationView
        
        //  ================= 自定义方法案例2(自定义大头针)
        
    }
    
    // MARK: - 选中一个大头针时调用的方法
    func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
        print("选中了大头针---\(view.annotation?.title)")
    }
    
    // MARK: - 取消选中某个大头针时调用的方法
    func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
        print("取消选中了大头针---\(view.annotation?.title)")
    }
}

运行效果:

自定义方法案例1(模拟系统的实现方案).gif

自定义方法案例2(自定义大头针).gif


步骤4:调整地图中心和显示区域

下面详细介绍下方案2:
步骤1:设置地图显示的中心

步骤2:设置跨度

步骤3:设置区域

步骤4:设置地图显示的区域

代码12:模拟追踪显示用户位置 Demo

编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0

【OC 语言】
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>

@interface ViewController ()<MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;   // 地图view
@property(nonatomic, strong) CLLocationManager *locationM; // 位置管理器
@end

@implementation ViewController

#pragma mark - 懒加载
- (CLLocationManager *)locationM {
    if (!_locationM) {
        // 创建位置管理者
        _locationM = [[CLLocationManager alloc] init];
        
        // 请求用户授权
        if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
            [_locationM requestAlwaysAuthorization];
        }
    }
    return _locationM;
}

#pragma mark - 地图显示用户位置
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self locationM]; //调用懒加载,请求用户授权
    
    // 显示用户位置:跟踪模式
    // 缺点:iOS8.0之前,地图不会自动滚到用户所在位置
    // 解决方案:设置地图代理,在地图获取用户位置代理方法中操作;设置地图显示中心/设置地图显示区域
    self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
    
    // 设置地图代理, 监听地图各个事件
    self.mapView.delegate = self;
}

#pragma mark - 当地图更新用户位置信息时, 调用的方法
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
    
    // 方案1:根据用户当前位置的经纬度,设置地图的中心,显示在当前用户所在的位置
    // 效果:地图会自动移动到指定的位置坐标,并显示在地图中心
    // 缺点:地图显示比例过大,无法调整,不会自动放大地图
    // 解决:直接使用对应的调整地图“显示区域”的API
    [mapView setCenterCoordinate:userLocation.coordinate animated:YES];
    
    /* 地图视图显示,不会更改地图的比例,会以地图视图高度或宽度较小的那个为基准,按比例调整
     MKCoordinateSpan 跨度解释
     latitudeDelta:纬度跨度,因为南北纬各90度,所以此值的范围是(0-180);此值表示整个地图视图宽度,显示多大跨度
     longitudeDelta:经度跨度,因为东西经各180度,所以此值范围是(0-360):此值表示整个地图视图高度,显示多大跨度
     */
    
    // 方案2:设置地图显示区域
    
    // ①使用地图的经纬度设置地图显示的中心
    // 中国地图全貌(纬度范围:3°51′N至53°33′N)(经度范围:73°33′E至135°05′E)
    CLLocationCoordinate2D center =CLLocationCoordinate2DMake(28, 104); // 使用地图的经纬度设置地图显示的中心
    MKCoordinateSpan span = MKCoordinateSpanMake(50, 64); // 设置跨度
    MKCoordinateRegion region = MKCoordinateRegionMake(center, span); //设置区域
    [self.mapView setRegion:region animated:YES];
    
    // ②使用区域中心设置地图显示的中心
    CLLocationCoordinate2D center =self.mapView.region.center; // 使用区域中心设置地图显示的中心
    MKCoordinateSpan span = MKCoordinateSpanMake(0.0219952102009202, 0.0160932558432023); //     设置跨度
    MKCoordinateRegion region = MKCoordinateRegionMake(center, span); // 设置区域
    [self.mapView setRegion:region animated:YES];
    
    // ③使用当前用户的位置设置地图显示的中心
    CLLocationCoordinate2D center = userLocation.coordinate;
    MKCoordinateSpan span =MKCoordinateSpanMake(0.0219952102009202, 0.0160932558432023); // 设置跨度
    MKCoordinateRegion region = MKCoordinateRegionMake(center, span); // 设置区域
    [mapView setRegion:region animated:YES];
}

#pragma mark - 区域改变的时候调用
// 应用场景:在不知道经纬度跨度的合适值的时候,将地图放大到自己想要的跨度,然后再控制台复制打印出来的跨度
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    // 打印经度和纬度的跨度
    NSLog(@"%f-%f", mapView.region.span.latitudeDelta,
          mapView.region.span.longitudeDelta);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
【Swift 语言】

import UIKit
import MapKit

class ViewController: UIViewController {

    @IBOutlet weak var mapView: MKMapView!// 地图view
    
    // 懒加载
    lazy var locationM: CLLocationManager = {
        
        let locationM = CLLocationManager()
        
        // 请求用户授权(判断设备的系统)(当前target为7.0)
        if #available(iOS 8.0, *) {
            locationM.requestAlwaysAuthorization()
        }
        return locationM
    }()
    
    // 地图显示用户位置
    override func viewDidLoad() {
        super.viewDidLoad()
        
        _ = locationM //调用懒加载,请求用户授权
        
        // 显示用户位置:跟踪模式
        // 缺点:iOS8.0之前,地图不会自动滚到用户所在位置
        // 解决方案:设置地图代理,在地图获取用户位置代理方法中操作;设置地图显示中心/设置地图显示区域
        mapView.userTrackingMode = MKUserTrackingMode.followWithHeading
        
        mapView.delegate = self
    }
}

extension ViewController: MKMapViewDelegate {
    
    // 当地图更新用户位置信息时, 调用的方法
    func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
        
        
        // MKUserLocation: 大头针数据模型
        // location : 者就是大头针的位置信息(经纬度)
        // heading: 设备朝向对象
        // title: 弹框标题
        // subtitle: 弹框子标题
        userLocation.title = "大头针弹框的标题"  // 大头针弹框的标题
        userLocation.subtitle = "大头针弹框的子标题" // 大头针弹框的子标题
        
        // 方案1:根据用户当前位置的经纬度,设置地图的中心,显示在当前用户所在的位置
        // 效果:地图会自动移动到指定的位置坐标,并显示在地图中心
        // 缺点:地图显示比例过大,无法调整,不会自动放大地图
        // 解决:直接使用对应的调整地图“显示区域”的API
        mapView.setCenter((userLocation.location?.coordinate)!, animated: true)
        
        
        /* 地图视图显示,不会更改地图的比例,会以地图视图高度或宽度较小的那个为基准,按比例调整
         MKCoordinateSpan 跨度解释
         latitudeDelta:纬度跨度,因为南北纬各90度,所以此值的范围是(0-180);此值表示整个地图视图宽度,显示多大跨度
         longitudeDelta:经度跨度,因为东西经各180度,所以此值范围是(0-360):此值表示整个地图视图高度,显示多大跨度
         */
        
        // 方案2:设置地图显示区域
        // ①使用地图的经纬度设置地图显示的中心
        // 中国地图全貌(纬度范围:3°51′N至53°33′N)(经度范围:73°33′E至135°05′E)
        let center = CLLocationCoordinate2DMake(28, 104) // 使用地图的经纬度设置地图显示的中心
        let span = MKCoordinateSpanMake(50, 64) // 设置跨度
        let region: MKCoordinateRegion = MKCoordinateRegionMake(center, span) // 设置区域
        mapView.setRegion(region, animated: true)
        
        // ②使用区域中心设置地图显示的中心
        let center = (mapView.region.center) // 使用区域中心设置地图显示的中心
        let span = MKCoordinateSpanMake(0.0219952102009202, 0.0160932558432023)// 设置跨度
        let region: MKCoordinateRegion = MKCoordinateRegionMake(center, span)// 设置区域
        mapView.setRegion(region, animated: true)
        
        // ③使用当前用户的位置设置地图显示的中心
        let center = (userLocation.location?.coordinate)!
        let span = MKCoordinateSpanMake(0.0219952102009202, 0.0160932558432023)// 设置跨度
        let region: MKCoordinateRegion = MKCoordinateRegionMake(center, span)// 设置区域
        mapView.setRegion(region, animated: true)
    }
    
    
    // 区域改变的时候调用
    // 应用场景:在不知道经纬度跨度的合适值的时候,将地图放大到自己想要的跨度,然后再控制台复制打印出来的跨度
    func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
        // 打印经度和纬度的跨度
        print(mapView.region.span.latitudeDelta, mapView.region.span.longitudeDelta)
    }
}

4.2 常见问题

4.3 测试环境



---

本文源码 Demo 详见 Github
https://github.com/shorfng/iOS_7.0_Device-Tools


作者:蓝田(Loto)
【作品发布平台】

简书
博客园
Gitbook(如果觉得文章太长,请阅读此平台发布的文章)

【代码托管平台】

Github

【如有疑问,请通过以下方式交流】

评论区回复
发送邮件shorfng@126.com


本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,谢谢合作。


如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
  • 支付宝扫一扫 向我打赏

  • 你也可以微信 向我打赏

posted @ 2017-03-29 17:38  蓝田_Loto  阅读(334)  评论(0编辑  收藏  举报