美文网首页iOS干货IOS开发资料库iOS技术难点
iOS学习笔记20-地图(二)MapKit框架

iOS学习笔记20-地图(二)MapKit框架

作者: 执着丶执念 | 来源:发表于2016-04-06 08:10 被阅读1571次

    一、地图开发介绍

    从iOS6.0开始地图数据不再由谷歌驱动,而是改用自家地图,当然在国内它的数据是由高德地图提供的。

    在iOS中进行地图开发主要有三种方式:
    • 利用MapKit框架进行地图开发,利用这种方式可以对地图进行精准的控制
    • 调用苹果官方自带的地图应用,主要用于一些简单的地图应用,无法精确控制
    • 使用第三方地图开发SDK库

    用得最多的还是MapKit,所以这节就只讲MapKit的使用。

    二、MapKit核心类

    MapKit的核心类为地图展示控件MKMapView,以下是常用的属性、对象方法以及代理方法。

    1. 属性:

    /* 用户位置跟踪 */
    @property (nonatomic) BOOL showsUserLocation;/*< 是否在地图上标注用户位置 */
    @property (nonatomic, readonly) MKUserLocation *userLocation;/*< 用户位置 */
    @property (nonatomic) MKUserTrackingMode userTrackingMode;/*< 用户跟踪类型 */
    typedef NS_ENUM(NSInteger, MKUserTrackingMode) {
        MKUserTrackingModeNone = 0, /*< 不跟踪 */
        MKUserTrackingModeFollow, /*< 跟踪 */
        MKUserTrackingModeFollowWithHeading,  /*< 导航跟踪 */
    };
    /* 设置地图配置项 */
    @property (nonatomic) MKMapType mapType;/*< 地图类型 */
    @property (nonatomic, readonly) NSArray *annotations;/*< 大头针数组 */
    typedef NS_ENUM(NSUInteger, MKMapType) {
        MKMapTypeStandard = 0,/*< 标准地图 */
        MKMapTypeSatellite,/*< 卫星地图 */
        MKMapTypeHybrid,/*< 混合模式(标准+卫星) */
        MKMapTypeSatelliteFlyover,/*< 3D立体卫星(iOS9.0) */
        MKMapTypeHybridFlyover,/*< 3D立体混合(iOS9.0) */
    }
    /* 设置地图控制项 */
    @property (nonatomic) BOOL zoomEnabled;/*< 是否可以缩放 */
    @property (nonatomic) BOOL scrollEnabled;/*< 是否可以滚动 */
    @property (nonatomic) BOOL rotateEnabled;/*< 是否可以旋转 */
    @property (nonatomic) BOOL pitchEnabled;/*< 是否显示3D视角 */
    /* 设置地图显示项 */
    @property (nonatomic) BOOL showsBuildings;/*< 是否显示建筑物,只影响标准地图 */
    @property (nonatomic) BOOL showsTraffic;/*< 是否显示交通,iOS9 */
    @property (nonatomic) BOOL showsCompass;/*< 是否显示指南针,iOS9 */
    @property (nonatomic) BOOL showsScale;/*< 是否显示比例尺,iOS9 */
    

    所谓大头针就是地图上显示的这个标注:


    大头针图片

    2. 对象方法:

    /* 添加大头针 */
    - (void)addAnnotation:(id <MKAnnotation>)annotation;
    - (void)addAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
    /* 删除大头针 */
    - (void)removeAnnotation:(id <MKAnnotation>)annotation;
    - (void)removeAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
    /* 选中大头针与取消选中大头针 */
    - (void)selectAnnotation:(id <MKAnnotation>)annotation 
                    animated:(BOOL)animated;
    - (void)deselectAnnotation:(id <MKAnnotation>)annotation 
                      animated:(BOOL)animated;
    /* 获取大头针视图 */
    - (MKAnnotationView *)viewForAnnotation:(id <MKAnnotation>)annotation;
    /* 从缓冲池中取出大头针视图控件 */
    - (MKAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier;
    /* 设置显示区域以及地图中心坐标 */
    - (void)setRegion:(MKCoordinateRegion)region 
             animated:(BOOL)animated;
    - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate 
                       animated:(BOOL)animated;
    /* 经纬度坐标转UIKit坐标,UIKit坐标转经纬度坐标 */
    - (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate 
                   toPointToView:(UIView *)view;
    - (CLLocationCoordinate2D)convertPoint:(CGPoint)point 
                      toCoordinateFromView:(UIView *)view;
    

    3. 常用代理方法MKMapViewDelegate

    /* 地图加载完成会调用 */
    - (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView;
    /* 地图加载失败会调用 */
    - (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error;
    /* 用户位置发生改变会调用 */
    - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation;
    /* 显示区域改变会调用 */
    - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
    /* 点击选中大头针时会调用 */
    - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view;
    /* 取消选中大头针时会调用 */
    - (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view;
    /* 显示地图上的大头针,功能类似于UITableView的tableView:cellForRowAtIndexPath:方法 */
    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation;
    

    三、MapKit使用

    1. 首先添加头文件:

    #import <MapKit/MapKit.h>
    

    2. 初始化地图展示控件MKMapView

    - (void)initMapView{
        CGFloat x = 0;
        CGFloat y = 20;
        CGFloat width = self.view.frame.size.width;
        CGFloat height = self.view.frame.size.height;
        //创建MKMapView,设置控件视图大小
        MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(x, y, width, height)];
        //设置地图类型
        mapView.mapType = MKMapTypeStandard;
        //设置代理
        mapView.delegate = self;
        [self.view addSubview:mapView];
        self.mapView = mapView;
    }
    

    3. 用户位置跟踪

    在iOS8之前,实现这个功能只需要:
    1. 设置用户跟踪模式
    1. mapView:DidUpdateUserLocation:代理方法中设置地图中心和显示范围
    在iOS8之后,用法稍有不同:
    1. 必须按照前面的定位章节的,获取前台或者前后台的定位服务授权,下面是链接:
      iOS学习笔记19-地图(一)定位CoreLocation
    1. 不需要进行中心点的指定,默认会将当前位置设置为中心点并自动显示区域范围
    2. 只有定位到当前位置后mapView:DidUpdateUserLocation:代理方法才会调用
    - (void)viewDidLoad {
        [super viewDidLoad];
        //获取定位服务授权
        [self requestUserLocationAuthor];
        //初始化MKMapView
        [self initMapView];
    }
    - (void)requestUserLocationAuthor{
        //如果没有获得定位授权,获取定位授权请求
        self.locationM = [[CLLocationManager alloc] init];
        if ([CLLocationManager locationServicesEnabled]) {
            if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedWhenInUse) {
                [self.locationM requestWhenInUseAuthorization];
            }
        }
    }
    - (void)initMapView{
        CGFloat x = 0;
        CGFloat y = 20;
        CGFloat width = self.view.frame.size.width;
        CGFloat height = self.view.frame.size.height;
        //创建MKMapView对象
        MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(x, y, width, height)];
        //设置地图类型
        mapView.mapType = MKMapTypeStandard;
        //设置用户跟踪模式
        mapView.userTrackingMode = MKUserTrackingModeFollow;
        mapView.delegate = self;
        [self.view addSubview:mapView];
        self.mapView = mapView;
    }
    #pragma mark - MKMapViewDelegate
    /* 更新用户位置会调用 */
    - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
        CLLocation *location = userLocation.location;
        CLLocationCoordinate2D coordinate = location.coordinate;
        NSLog(@"经度:%f,纬度:%f",coordinate.latitude,coordinate.longitude);
    }
    
    用户位置跟踪

    4. 添加大头针

    MapKit没有自带的大头针,只有大头针协议MKAnnotation,我们需要自定义大头针:

    1. 创建一个继承NSObject的类
    1. 实现MKAnnotation协议
    2. 必须创建一个属性,用于存储大头针位置
    @property (nonatomic) CLLocationCoordinate2D coordinate;
    
    下面就是我简单创建的LTAnnotation类:
    #import <Foundation/Foundation.h>
    #import <MapKit/MapKit.h>
    
    @interface LTAnnotation : NSObject <MKAnnotation>
    /* 必须创建的属性 */
    @property (nonatomic) CLLocationCoordinate2D coordinate;
    /* 可选的属性 */
    @property (nonatomic, copy) NSString *title;
    @property (nonatomic, copy) NSString *subtitle;
    /* 自定义的属性 */
    @property (nonatomic, strong) UIImage *icon;
    @end
    
    @implementation LTAnnotation
    @end
    
    下面是实际的使用:
    - (void)viewDidLoad {
        [super viewDidLoad];
        //请求定位授权
        [self requestUserLocationAuthor];
        //初始化MKMapView
        [self initMapView];
        //添加大头针
        [self addAnnotationsToMapView];
    }
    - (void)addAnnotationsToMapView{
        CLLocationCoordinate2D location1 = CLLocationCoordinate2DMake(22.54, 114.02);
        //创建大头针
        LTAnnotation *annotation = [[LTAnnotation alloc] init];
        annotation.title = @"执着";
        annotation.subtitle = @"执着哥开的店";
        annotation.coordinate = location1;
        annotation.icon = [UIImage imageNamed:@"red"];
        //添加大头针
        [self.mapView addAnnotation:annotation1];
    }
    
    大头针在地图上的显示 点击大头针显示

    5. 自定义大头针视图

    上面的大头针样子是不是很丑,那是MKMapView的默认样式大头针视图MKAnnotationView,我们先来了解下它的常用属性:

    @property (nonatomic, strong) id<MKAnnotation> annotation;/*< 大头针数据 */
    @property (nonatomic, strong) UIImage *image;/*< 大头针的图标 */
    @property (nonatomic, readonly) NSString *reuseIdentifier;/*< 大头针的唯一标示 */
    @property (nonatomic) CGPoint calloutOffset;/*< 弹出视图的偏移 */
    @property (nonatomic) BOOL selected;/*< 是否选中 */
    @property (nonatomic) BOOL canShowCallout;/*< 是否能点击弹出视图 */
    @property (nonatomic, strong) UIView *leftCalloutAccessoryView;/*< 弹出视图左边的视图 */
    @property (nonatomic, strong) UIView *rightCalloutAccessoryView;/*< 弹出视图右边的视图 */
    
    下面是通过设置MKAnnotationView的属性,自定义大头针视图:
    /* 每当大头针显示在可视界面上时,就会调用该方法,用户位置的蓝色点也是个大头针,也会调用 */
    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
    {
        if ([annotation isKindOfClass:[LTAnnotation class]]) {
            LTAnnotation *annotationLT = (LTAnnotation *)annotation;
            //类似于UITableViewCell的重用机制,大头针视图也有重用机制
            static NSString *key = @"AnnotationIdentifier";
            MKAnnotationView *view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:key];
            if (!view) {
                view = [[MKAnnotationView alloc] initWithAnnotation:annotation
                                                    reuseIdentifier:key];
            }
            //设置大头针数据
            view.annotation = annotation;
            //自定义大头针默认是NO,表示不能弹出视图,这里让大头针可以点击弹出视图
            view.canShowCallout = YES;
            //设置大头针图标
            view.image = annotationLT.icon;
            //设置弹出视图的左边视图
            UIImage *leftImage = [UIImage imageNamed:@"cafeIcon"];
            UIImageView *leftView = [[UIImageView alloc] initWithImage: leftImage];
            leftView.bounds = CGRectMake(0, 0, 50, 50);
            view.leftCalloutAccessoryView = leftView;
            //设置弹出视图的右边视图
            UIImage *rightImage = [UIImage imageNamed:@"cafeRight"];
            UIImageView *rightView = [[UIImageView alloc] initWithImage: rightImage];
            rightView.bounds = CGRectMake(0, 0, 50, 50);
            view.rightCalloutAccessoryView = rightView;
            return view;
        }
        //返回nil,表示显示默认样式
        return nil;
    }
    
    改变默认样式的大头针视图

    四、扩展--自定义大头针弹出详情视图

    如果你去关注下一些地图应用,会发现他们的弹出视图和我们的完全不一样,那是怎么实现的呢?

    实际上那不是弹出视图,那是个大头针,只是这个大头针做得和弹出视图很像而已。

    实现思路:
    1. 当点击普通的大头针时,移除地图上其他的详情大头针,添加当前大头针的详情大头针
    2. 当普通大头针取消选中时,移除地图上所有的详情大头针
    3. mapView:viewForAnnotation:方法中设置普通大头针视图和详情大头针视图
    下面是实现的部分代码【实现效果比较随便,见谅】:
    #pragma mark - 地图控件代理方法
    /* 显示大头针时调用,注意方法中的annotation参数是即将显示的大头针对象 */
    -(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
        //由于当前位置的标注也是一个大头针,所以此时需要判断,此代理方法返回nil使用默认大头针视图
        if ([annotation isKindOfClass:[LTAnnotation class]]) {
            static NSString *key1 = @"AnnotationKey1";
            MKAnnotationView *annotationView = [_mapView dequeueReusableAnnotationViewWithIdentifier:key1];
            //如果缓存池中不存在则新建
            if (!annotationView) {
                annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
                                                              reuseIdentifier:key1];
                annotationView.canShowCallout = NO;//不允许弹出视图,但可以被选中
            }
            //重新设置此类大头针视图的大头针模型(因为有可能是从缓存池中取出来的,位置是放到缓存池时的位置)
            annotationView.annotation = annotation;
            annotationView.image = ((LTAnnotation *)annotation).icon;//设置大头针视图的图片
            return annotationView;
        }else if([annotation isKindOfClass:[LTCalloutAnnotation class]]){
            static NSString *key2 = @"AnnotationCallOutKey2";
            MKAnnotationView *calloutView = [_mapView dequeueReusableAnnotationViewWithIdentifier:key2];
            //如果缓存池中不存在则新建
            if (!calloutView) {
                calloutView = [[MKAnnotationView alloc] initWithAnnotation:annotation
                                                           reuseIdentifier:key2];
                calloutView.canShowCallout = NO;//不允许弹出视图,但可以被选中
            }
            //对于作为弹出详情视图的自定义大头针视图无弹出交互功能,在其中可以自由添加其他视图
            calloutView.annotation = annotation;
            //设置详情大头针的偏移位置
            calloutView.centerOffset = CGPointMake(-50, -80);
            [self calloutAddSubView:calloutView];
            return calloutView;
        } else {
            return nil;
        }
    }
    

    上面我的LTCalloutAnnotation和LTAnnotation实际上是只是类名不同而已,属性都一样。

    #pragma mark 添加弹出视图的子控件,这里我就很随便了,你可以搞得好看点
    - (void)calloutAddSubView:(MKAnnotationView *)calloutView
    {
        //添加背景
        UIView *background = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 60)];
        background.backgroundColor = [UIColor whiteColor];
        background.layer.borderWidth = 5;
        background.layer.borderColor = [UIColor blueColor].CGColor;
        [calloutView addSubview:background];
        //添加图片
        UIImage *image = [UIImage imageNamed:@"cafeRight"];
        UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
        imageView.frame = CGRectMake(5, 5, 50, 50);
        [calloutView addSubview:imageView];
        //添加一个红色方块
        UIView *subview = [[UIView alloc] initWithFrame:CGRectMake(60, 5, 35, 40)];
        subview.backgroundColor = [UIColor redColor];
        [calloutView addSubview:subview];
    }
    
    #pragma mark 选中大头针时触发
    //点击一般的大头针KCAnnotation时添加一个大头针作为所点大头针的弹出详情视图
    -(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
        if ([view.annotation isKindOfClass:[LTAnnotation class]]) {
            LTAnnotation *annotation = view.annotation;
            //点击一个大头针时移除其他弹出详情视图
            [self removeCalloutAnnotation];
            //添加详情大头针
            LTCalloutAnnotation *callout = [[LTCalloutAnnotation alloc] init];
            callout.icon = annotation.icon;
            callout.title = annotation.title;
            callout.subtitle = annotation.subtitle;
            callout.coordinate = annotation.coordinate;
            [self.mapView addAnnotation:callout];
        }
    }
    #pragma mark 取消选中时触发
    -(void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{
        [self removeCalloutAnnotation];
    }
    #pragma mark 移除所用详情大头针
    -(void)removeCalloutAnnotation{
        [self.mapView.annotations enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop){
            if ([obj isKindOfClass:[LTCalloutAnnotation class]]) {
                [_mapView removeAnnotation:obj];
            }
        }];
    }
    
    自定义弹出视图的效果图

    这个自定义弹出详情视图,我做的比较简陋,我主要是为了好说明具体是怎么实现的,你可以把弹出界面做的好看点,顺便把一些大头针视图进行下封装,那一切就很完美了,O(∩_∩)O哈!这种实现是很低效的,每次都需要遍历所有的大头针,从中找到详情大头针,需要优化的地方很多,可以自己去想着优化。

    如果有什么意见请在下方评论区写出来,求关注,求打赏,O(∩_∩)O哈!

    相关文章

      网友评论

      • 奔跑的蔬菜:使用系统地图在拖动,缩放的时候内存激增. 有的时候还崩溃了. 请问楼主是怎么解决的
      • 晚熟人:UIImage *image1 = [UIImage imageNamed:@"q@2x"];
        annoation1.icon = image1;
        为什么图片显示不出来啊,几遍我设置
        image1 = [image1 imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
        也不行。
        晚熟人:@忆如初 解决了
      • Yacong:拖动 和 缩放地图内存会不断增加,不知有没遇到这样问题
      • 浪尖ON的水滴:为啥我第一次启动的时候没有定位到我当前的位置呢,已经用户授权啦
        浪尖ON的水滴:@执着_执念 真机,那个方法就没有调用
        执着丶执念:@执着_执念 如果是模拟器需要设置
        执着丶执念:@浪尖上的水滴 你是真机还是模拟器
      • 听小马儿说:有没有写一些三方的SDK的笔记呢.还有像划线导航的功能呢
        执着丶执念:@EowynQ 这个估计以后会写

      本文标题:iOS学习笔记20-地图(二)MapKit框架

      本文链接:https://www.haomeiwen.com/subject/gmrplttx.html