zoukankan      html  css  js  c++  java
  • 【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,  40, height: 40))
            leftIMV.image = UIImage(named: "huba")
            annotationView?.leftCalloutAccessoryView = leftIMV
            
            // 6.2 设置弹框右侧视图
            let rightIMV = UIImageView(frame: CGRect(x: 0, y: 0,  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


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


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

    • 你也可以微信 向我打赏

  • 相关阅读:
    [转载]C#流,字节数组,字符串
    [C#错误]未找到类型或命名空间名称" " (是否缺少 using 指令或程序集引用?)
    批处理删除带空格的长目录或文件夹
    注释换行 引号内的字符串没有正确结束
    oracle sqlplus运行sql文件
    Target runtime Apache Tomcat v6.0 is not defined.错误解决方法
    Tomcat:Caused by: java.lang.OutOfMemoryError: PermGen space
    虚拟机vmware启动太快无法进入bios的解决方法
    MySQL Error 1130 Host 'localhost' is not allowed to connect to this MySQL server
    如何远程判断服务器的操作系统?
  • 原文地址:https://www.cnblogs.com/shorfng/p/6640876.html
Copyright © 2011-2022 走看看