地图系列之二 MapKit框架

简介

  1. MapKit框架使用前提
    1. 导入框架(Xcode5之后可以省略,前提:在代码里面使用了框架中某个类创建了一个对象!!!!)
    2. 导入主头文件#import <MapKit/MapKit.h>
    3. 常见错误:
      1. 在SB中拖了一个MKMapView,也导入了主头文件,但是在运行时会报错,因为代码中没有使用该类的任何一个方法,所以该框架实际上还未自动导入(当应用程序第一次使用该类时,会导入)
  2. MapKit框架使用须知
    1. MapKit框架中所有数据类型的前缀都是MK
    2. MapKit有一个比较重要的UI控件 :MKMapView,专门用于地图显示

MKMapView

  1. 地图的简单展示
    1. 在SB中拖入一个MKMapView控件,布局,属性脱线到控制器中
    2. 在控制器中导入#import <MapKit/MapKit.h>
    3. 运行即可展示了

MKMapView常用的属性及方法

  1. 常用属性:

     //是否允许缩放
     @property (nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;
     //是否可滚动
     @property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
     //是否可旋转 rotateEnabled
     @property (nonatomic, getter=isRotateEnabled) BOOL rotateEnabled;
     是否显示指南针(iOS9.0)
     @property (nonatomic) BOOL showsCompass;
     //是否显示比例尺(iOS9.0)
     @property (nonatomic) BOOL showsScale;
     //是否显示交通(iOS9.0)
     @property (nonatomic) BOOL showsTraffic;
     //是否显示建筑(iOS9.0)
     @property (nonatomic) BOOL showsBuildings;
     //是否显示用户位置
     @property (nonatomic) BOOL showsUserLocation;
     //获取用户当前的大头针模型
     @property (nonatomic, readonly) MKUserLocation *userLocation;
     //设置地图的类型
     @property (nonatomic) MKMapType mapType;
     /*
     MKMapType枚举值:
     MKMapTypeStandard :普通地图
     MKMapTypeSatellite :卫星云图 
     MKMapTypeHybrid :混合模式(普通地图覆盖于卫星云图之上 )
     MKMapTypeSatelliteFlyover: 3D立体卫星 (iOS9.0)
     MKMapTypeHybridFlyover: 3D立体混合(iOS9.0)
     */
     //跟踪并显示用户位置的方式
     @property (nonatomic) MKUserTrackingMode userTrackingMode;
     /*
     MKUserTrackingModeNone :不跟踪用户的位置
     MKUserTrackingModeFollow :跟踪并在地图上显示用户的当前位置
     MKUserTrackingModeFollowWithHeading :跟踪并在地图上显示用户的当前位置,地图会跟随用户的前进方向进行旋转
     */
    

设置地图的显示

  1. 通过MKMapView的下列属性以及方法,可以设置地图显示的位置和区域
    1. 设置地图的中心点位置

       @property (nonatomic) CLLocationCoordinate2D centerCoordinate;
       - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated;
      
    2. 设置地图的显示区域(地图所显示的跨度)

       @property (nonatomic) MKCoordinateRegion region;
       - (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated;
      
      1. MKCoordinateRegion是一个用来表示区域的结构体,定义如下

         typedef struct {
             CLLocationCoordinate2D center; // 区域的中心点位置
             MKCoordinateSpan span; // 区域的跨度
         } MKCoordinateRegion;
                        
         MKCoordinateSpan的定义
         typedef struct {
             CLLocationDegrees latitudeDelta; // 纬度跨度
             CLLocationDegrees longitudeDelta; // 经度跨度
         } MKCoordinateSpan;
        
  2. 使用注意
    1. 设置对应的属性时,注意该属性是从哪个系统版本开始引入的,做好不同系统版本的适配
    2. 设置对应的属性时,一定要看清楚该属性只针对哪类地图是有效的

显示用户的位置

  1. 方式一: 显示用户的位置
    1. 设置MKMapView的showsUserLocation属性可以显示用户的当前位置
    2. 下图是显示效果
      1. 蓝色发光圆点就是用户的当前位置
      2. 蓝色发光圆点,专业术语叫做“大头针”
      3. 注意:iOS8.0之后,显示用户位置需要用户进行定位授权
        1. 导入CoreLocation框架
        2. 创建CLLocationManager,然后使用它请求用户授权
      4. 特点:
        1. 显示一个蓝点,在地图上标示用户的位置信息
        2. 不会自动放大地图,并且当用户位置移动时,地图不会自动跟着跑
  2. 方式二: 追踪用户的位置(常用)
    1. 设置MKMapView的userTrackingMode属性可以跟踪显示用户的当前位置
    2. 特点:(与上面对比)
      1. 显示一个蓝点,在地图上标示用户的位置信息
      2. 会自动放大地图,并且当用户位置移动时,地图会自动跟着跑
    3. 注意:iOS8.0之后,显示用户位置需要用户进行定位授权
    4. 备注:
      1. iOS8.0-,地图不会自动滚动到用户所在位置
      2. iOS8.0+,地图会自动放大到合适比例,并显示出用户位置

MKMapView的代理

  1. MKMapView可以设置一个代理对象,用来监听地图的相关行为
  2. 常见的代理方法有

     //一个位置更改默认只会调用一次,不断监测用户的当前位置
     //每次调用,都会把用户的最新位置(userLocation参数)传进来,userLocation表示地图上用户位置那颗大头针
     - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation;
     //地图的显示区域即将发生改变的时候调用
     - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
     //地图的显示区域已经发生改变的时候调用
     - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
    

MKUserLocation类:

  1. 这个类是系统自带的大头针模型类,就是用户位置显示的那颗大头针
  2. 一个类只要遵守了协议就是一个大头针类型,该类就是遵守了这个协议
  3. 包括以下属性

     //显示在大头针上的标题
     @property (nonatomic, copy) NSString *title;
     //显示在大头针上的子标题
     @property (nonatomic, copy) NSString *subtitle;
     //地理位置信息(大头针钉在什么地方?)
     @property (readonly, nonatomic) CLLocation *location;
    

代码举例:

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

@implementation ViewController
-(CLLocationManager *)locationManager{
    if (_locationManager == nil) {
        _locationManager = [[CLLocationManager alloc] init];
    }
    return _locationManager ;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //请求定位iOS8.0+
    [self.locationManager requestWhenInUseAuthorization];
    [self.locationManager requestAlwaysAuthorization];
    //设置地图的类型
    self.mapView.mapType = MKMapTypeStandard;
    
    // 显示用户的位置
    //方式一: 只显示不会追踪
//    self.mapView.showsUserLocation = YES;
    //方式二: 跟踪用户位置
    self.mapView.userTrackingMode = MKUserTrackingModeFollow;
    
    //设置地图代理
    self.mapView.delegate = self;
}

#pragma  mark -MKMapViewDelegate
/*
 当用户的位置更新,就会调用(不断地监控用户的位置,调用频率特别高)
 userLocation 表示地图上蓝色那颗大头针的数据
 */
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
    //1.给userLocation设置数据
    userLocation.title = @"我是主标题";
    userLocation.subtitle = @"我是子标题";
    //2. 设置显示地图的中心点(将当前用户的位置设置为地图的中心点)
    [mapView setCenterCoordinate:userLocation.location.coordinate animated:YES];
    //3. 设置地图的展示范围
    //我们如何拿到这个范围呢: 1. 实现下面的代理方法. 2. 拖动地图放大,一直放到认为合适的位置 3	. 在下面代理方法中,拿到相应的span
     MKCoordinateSpan span = MKCoordinateSpanMake(0.021321, 0.019366);
     MKCoordinateRegion region = MKCoordinateRegionMake(userLocation.location.coordinate, span);
    [mapView setRegion:region animated:YES];
}
/*
 用户缩放时,就会调用,该方法可用于调试出适合的跨度,然后用到setRegion方法中
 */
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
        NSLog(@"%f %f", mapView.region.span.latitudeDelta, mapView.region.span.longitudeDelta);
}
@end

大头针

  1. 什么是大头针?
    1. 钉在某个具体位置,用来标识这个位置上有特定的事物(比如这个位置上有家餐馆)

大头针的基本使用

  1. 常用的方法(以下都是MapView的方法)

     //添加一个大头针
     - (void)addAnnotation:(id <MKAnnotation>)annotation;
     //添加多个大头针
     - (void)addAnnotations:(NSArray *)annotations;
     //移除一个大头针
     - (void)removeAnnotation:(id <MKAnnotation>)annotation;
     //移除多个大头针
     - (void)removeAnnotations:(NSArray *)annotations;
    
  2. (id <MKAnnotation>)annotation参数是什么东西?
    1. 大头针模型对象:用来封装大头针的数据,比如大头针的位置、标题、子标题等数据
    2. 只要任意一个对象遵守了<MKAnnotation>协议,并且实现协议中的@required,则就是大头针模型对象
    3. 当调用了(添加大头针方法)时,系统就会自动创建一个大头针视图,内部数据由该模型对象决定
  3. 自定义大头针模型
    1. 为何要自定义大头针模型? 为何不使用系统自动带的大头针模型(MKUserLocation)呢?
      1. 因为系统自带的大头针MKUserLocation类的location属性为readonly,不能修改
      2. 当添加一个大头针时,位置肯定是不定的,因此需要自定义大头针模型
    2. 代码如下:

       //1. 自定义大头针模型
       #import <Foundation/Foundation.h>
       #import <MapKit/MapKit.h>
       @interface ZHAnnotation : NSObject<MKAnnotation>
              
       /** 坐标位置 */
       @property (nonatomic,assign) CLLocationCoordinate2D coordinate;
       /** 标题 */
       @property (nonatomic, copy) NSString *title;
       /** 子标题 */
       @property (nonatomic, copy) NSString *subtitle;
       @end
              
       #import "ZHAnnotation.h"
       @implementation ZHAnnotation
       @end
              
       //2. 添加大头针
              
       #import "ViewController.h"
       #import <MapKit/MapKit.h>
       #import <CoreLocation/CoreLocation.h>
       #import "ZHAnnotation.h"
       @interface ViewController ()<MKMapViewDelegate>
       @property (weak, nonatomic) IBOutlet MKMapView *mapView;
       @property (nonatomic,strong) CLLocationManager *locationManager;
              
       @end
              
       @implementation ViewController
       -(CLLocationManager *)locationManager{
           if (_locationManager == nil) {
               _locationManager = [[CLLocationManager alloc] init];
           }
           return _locationManager ;
       }
              
       - (void)viewDidLoad {
           [super viewDidLoad];
           //请求定位iOS8.0+
           [self.locationManager requestWhenInUseAuthorization];
           [self.locationManager requestAlwaysAuthorization];
           //设置地图的类型
           self.mapView.mapType = MKMapTypeStandard;
           //1. 跟踪用户位置
           self.mapView.userTrackingMode = MKUserTrackingModeFollow;
           //2. 设置地图代理
           self.mapView.delegate = self;
           // 3. 监听mapView的点击事件
           [self.mapView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapMapView:)]];
       }
              
       /**
        *  监听mapView的点击
        */
       - (void)tapMapView:(UITapGestureRecognizer *)tap{
           // 1.获得用户在mapView点击的位置(x,y)
           CGPoint point = [tap locationInView:tap.view];
                  
           // 2.将数学坐标 转为 地理经纬度坐标
           CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
                  
           // 3.创建大头针模型,添加大头针到地图上
           //创建大头针
           ZHAnnotation *anno = [[ZHAnnotation alloc] init];
           anno.title = @"自定义主标题";
           anno.subtitle = @"自定义子标题";
           anno.coordinate = coordinate;
           [self.mapView addAnnotation:anno];
                  
           //随机大量添加
           //中国的经纬度范围
           // 纬度范围:N 3°51′ ~  N 53°33′
           // 经度范围:E 73°33′ ~  E 135°05′
                  
           //    for (int i = 0; i<1000; i++) {
           //        ZHAnnotation *anno = [[ZHAnnotation alloc] init];
           //        CLLocationDegrees latitude = 4 + arc4random_uniform(50);
           //        CLLocationDegrees longitude = 73 + arc4random_uniform(60);
           //        anno.coordinate = CLLocationCoordinate2DMake(latitude, longitude);
           //        anno.title = @"自定义主标题";
           //        anno.subtitle = @"自定义子标题";
           //        [self.mapView addAnnotation:anno];
           //    }
           }
              
       pragma  mark -MKMapViewDelegate
       /*
        当用户的位置更新,就会调用(不断地监控用户的位置,调用频率特别高)
        userLocation 表示地图上蓝色那颗大头针的数据
       */
       - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
           //1.给userLocation设置数据
           userLocation.title = @"我是主标题";
           userLocation.subtitle = @"我是子标题";
           //2. 设置显示地图的中心点(将当前用户的位置设置为地图的中心点)
           [mapView setCenterCoordinate:userLocation.location.coordinate animated:YES];
           //3. 设置地图的展示范围
            MKCoordinateSpan span = MKCoordinateSpanMake(0.021321, 0.019366);
            MKCoordinateRegion region = MKCoordinateRegionMake(userLocation.location.coordinate, span);
           [mapView setRegion:region animated:YES];
       }
      

自定义大头针

  1. 上面只是自定义了大头针显示的模型数据,并没有自定义大头针的显示样式
  2. 如何自定义大头针显示视图样式?
    1. 设置MKMapView的代理
    2. 实现MKMapView下面的代理方法,返回大头针控件

       //根据传进来的(id <MKAnnotation>)annotation参数创建并返回对应的大头针视图控件
       //根据模型来创建view返回出去
       - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;
      
      1. 调用时刻:
        1. 添加一个大头针或者显示用户的位置(用户显示在地图上时,那个蓝点也是一个大头针,并且是(MKUserLocation *)userLocation系统自带的大头针模型)
        2. 即:只要地图上一有大头针,就会调用该代理方法,并把该大头针模型原型传递进来
      2. 如果返回nil,显示出来的大头针就采取系统的默认样式
      3. 标识用户位置的蓝色发光圆点,它也是一个大头针,当显示这个大头针时,也会调用代理方法
      4. 因此,需要在代理方法中分清楚(id )annotation参数代表自定义的大头针还是蓝色发光圆点
      5. MKAnnotationView方法返回值
        1. 大头针视图控件
        2. 就是大头针那个View

MKAnnotationView类

  1. 地图上的大头针控件是MKAnnotationView
  2. MKAnnotationView的属性

     //大头针模型(当大头针显示时,会到该模型中取数据)
     @property (nonatomic, strong) id <MKAnnotation> annotation;
     //显示的图片(将大头针以图片的形式展示,若不设默认系统样式)
     @property (nonatomic, strong) UIImage *image;
     //是否显示标注(注意: 大头针一旦自定义,默认为NO)
     @property (nonatomic) BOOL canShowCallout;
     //标注的偏移量
     @property (nonatomic) CGPoint calloutOffset;
     //标注右边显示什么控件
     @property (strong, nonatomic) UIView *rightCalloutAccessoryView;
     //标注左边显示什么控件
     @property (strong, nonatomic) UIView *leftCalloutAccessoryView;
     //标注下面显示什么控件(iOS9.0)
     @property (nonatomic, strong) UIView *detailCalloutAccessoryView
    

MKPinAnnotationView

  1. MKPinAnnotationView是MKAnnotationView的子类
  2. MKPinAnnotationView比MKAnnotationView多了2个属性

     //大头针颜色
     @property (nonatomic) MKPinAnnotationColor pinColor;
     //大头针第一次显示时是否从天而降
     @property (nonatomic) BOOL animatesDrop;
        
    
  3. 注意: 如果使用了该子类,父类的image属性无效

代码举例:

#import "ViewController.h"
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "ZHAnnotation.h"
@interface ViewController ()<MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (nonatomic,strong) CLLocationManager *locationManager;
    
@end
    
@implementation ViewController
-(CLLocationManager *)locationManager{
    if (_locationManager == nil) {
        _locationManager = [[CLLocationManager alloc] init];
    }
    return _locationManager ;
}
    
- (void)viewDidLoad {
    [super viewDidLoad];
    //请求定位iOS8.0+
    [self.locationManager requestWhenInUseAuthorization];
    [self.locationManager requestAlwaysAuthorization];
    
    self.mapView.userTrackingMode = MKUserTrackingModeFollow;
    //设置地图代理
    self.mapView.delegate = self;
}
    
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //创建大头针
    //餐厅模型
    ZHAnnotation *anno = [[ZHAnnotation alloc] init];
    anno.title = @"饭店";
    anno.subtitle = @"饭店子标题";
    //给该模型添加一个icon属性!!!!
    anno.icon = @"category_1";
    anno.coordinate = CLLocationCoordinate2DMake(39, 115);
    [self.mapView addAnnotation:anno];
    
    //电影
    ZHAnnotation *anno2 = [[ZHAnnotation alloc] init];
    anno2.title = @"影院";
    anno2.subtitle = @"影院子标题";
    anno2.icon = @"category_5";
    anno2.coordinate = CLLocationCoordinate2DMake(39, 116);
    [self.mapView addAnnotation:anno2];
}
#pragma  mark -MKMapViewDelegate
    
/*
 该方法中大头针也是循环利用的
 
 */
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
    
    //用户位置这个大头针不是ZHAnnotation 而是(MKUserLocation *)userLocation
    if (![annotation isKindOfClass:[ZHAnnotation class]]) return nil;
    
    static NSString *ID = @"tuangou";
    // 从缓存池中取出可以循环利用的大头针view
    MKAnnotationView *annoView = [mapView dequeueReusableAnnotationViewWithIdentifier:ID];
    if (annoView == nil) {
        annoView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ID];
        //点击大头针,显示标题和子标题
        annoView.canShowCallout = YES;
        //设置大头针的偏移量
        annoView.calloutOffset = CGPointMake(0, -10);
        //设置大头针描述右边的内容
        annoView.rightCalloutAccessoryView = [UISwitch new];
        //设置大头针描述左边的内容
        annoView.leftCalloutAccessoryView = [UISwitch new];
    }
    
    // 传递模型
    annoView.annotation = annotation;
    //设置图片
    ZHAnnotation *anno = annotation;
    annoView.image = [UIImage imageNamed:anno.icon];
    
    return annoView;
}
    
// MKPinAnnotationView的使用
//- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
//{
//    if (![annotation isKindOfClass:[ZHAnnotation class]]) return nil;
//
//    static NSString *ID = @"tuangou";
//    // 从缓存池中取出可以循环利用的大头针view
//    MKPinAnnotationView *annoView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:ID];
//    if (annoView == nil) {
//        annoView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ID];
//        annoView.canShowCallout = YES;
//        // 设置从天而降的动画
//        annoView.animatesDrop = YES;
//    }
//
//    // 传递模型
//    annoView.annotation = annotation;
//
//    return annoView;
//}
@end
Table of Contents