自定义高德地图大头针气泡

作者: 追沐 | 来源:发表于2017-06-15 15:16 被阅读926次

    当高德地图自带的大头针气泡不能满足项目需求时,需要开发者自定义地图大头针气泡。比如气泡上显示个图片,标题或者其他之类的。

    一、高德地图“气泡”的本质是什么?

    • 大头针视图
      大头针显示在地图上是一个图片,图片是放在imageView上的。高德地图上先是有一个view(MAAnnotationView),然后这个view上有一个imageView,imageView上放一张图片,就是我们在地图上所看到的大头针了。

    • 大头针气泡视图
      大头针气泡(CalloutView)一般是显示在大头针(MAPointAnnotation)顶部的一个视图,归根结底是一个view。它是添加在大头针视图(MAAnnotationView)上的一个视图。

    • MAAnnotationView、CalloutView、MAPointAnnotation
      MAAnnotationView是高德地图提供的大头针视图view,继承自UIView,高德对其进行了封装,有很多属性可以看看高德文档。其中每个MAAnnotationView都关联一个annotation,annotation是MAPointAnnotation的对象。
      CalloutView是大头针气泡视图,高德自己的没有完全暴露在外部。
      MAPointAnnotation,每个大头针视图都关联一个annotation,annotation有title、subtitle、还有经纬度信息。就把它看做是一个给MAAnnotationView提供数据支撑的model吧。

    二、自定义高德地图大头针气泡

    要实现自定义的大头针气泡,不能用高德自己的大头针视图MAAnnotationView,因为我们要给MAAnnotationView增加一个calloutView属性来实现自定义的大头针气泡功能。

    需要三步:

    1、这里我们只需要继承自MAAnnotationView来创建一个自己的大头针视图:DDCustomAnnotationView。

    2、然后在自定义一个继承自UIView的视图作为气泡:DDCustomCalloutView。

    3、在地图的代理方法中设置自定义的大头针气泡

    第一步:自定义一个继承UIView的大头针气泡视图DDCustomCalloutView

    DDCustomCalloutView.h
    #import <UIKit/UIKit.h>
    
    @interface DDCustomCalloutView : UIView
    
    @property (nonatomic, strong) UILabel *textLabel;//气泡上的label
    
    @property (nonatomic, strong) UILabel *leftNumLabel;//气泡上的数字标记
    
    @end
    
    
    • 这里只自定义了一个label,大家可根据自己的实际情况自己定义更加丰富的气泡视图都是可以的。
    DDCustomCalloutView.m
    #import "DDCustomCalloutView.h"
    
    #define kArrorHeight 10
    
    @implementation DDCustomCalloutView
    
    - (void)drawRect:(CGRect)rect
    {
        CGFloat width = rect.size.width;
        CGFloat height = rect.size.height-kArrorHeight;
        //圆角半径
        CGFloat radius = (self.frame.size.height-kArrorHeight)*0.5;
        
        CGContextRef context = UIGraphicsGetCurrentContext();
        // 移动到初始点
        CGContextMoveToPoint(context, radius, 0);
        // 绘制第1条线和第1个1/4圆弧
        CGContextAddLineToPoint(context, width - radius, 0);
        CGContextAddArc(context, width - radius, radius, radius, -0.5 * M_PI, 0.0, 0);
        // 绘制第2条线和第2个1/4圆弧
        CGContextAddLineToPoint(context, width, height - radius);
        CGContextAddArc(context, width - radius, height - radius, radius, 0.0, 0.5 * M_PI, 0);
        // 绘制第3条线和第3个1/4圆弧
        CGContextAddLineToPoint(context, radius, height);
        CGContextAddArc(context, radius, height - radius, radius, 0.5 * M_PI, M_PI, 0);
        // 绘制第4条线和第4个1/4圆弧
        CGContextAddLineToPoint(context, 0, radius);
        CGContextAddArc(context, radius, radius, radius, M_PI, 1.5 * M_PI, 0);
        // 闭合路径
        CGContextClosePath(context);
        // 填充半透明黑色
        CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
        CGContextDrawPath(context, kCGPathFill);
        
        self.layer.shadowColor = [[UIColor grayColor] CGColor];
        self.layer.shadowOpacity = 1.0;
        self.layer.shadowOffset = CGSizeMake(0.0f, 2.0f);
    }
    
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
            
            self.backgroundColor = [UIColor clearColor];
            
            [self setUpUI];
        }
        return self;
    }
    
    #pragma mark UI
    - (void)setUpUI {
        self.textLabel = [[UILabel alloc]initWithFrame:CGRectMake(8, 0, self.frame.size.width-16, self.frame.size.height-kArrorHeight)];
        self.textLabel.backgroundColor = [UIColor clearColor];
        self.textLabel.textAlignment = NSTextAlignmentCenter;
        self.textLabel.font = [UIFont systemFontOfSize:15.0];
        self.textLabel.textColor = [UIColor lightGrayColor];
        [self addSubview:self.textLabel];
    
        self.leftNumLabel = [[UILabel alloc]initWithFrame:CGRectMake(8, 0, 20, self.frame.size.height-kArrorHeight)];
        self.leftNumLabel.backgroundColor = [UIColor clearColor];
        self.leftNumLabel.textAlignment = NSTextAlignmentCenter;
        self.leftNumLabel.font = [UIFont systemFontOfSize:15.0];
        self.leftNumLabel.textColor = [UIColor redColor];
        [self addSubview:self.leftNumLabel];
    }
    
    @end
    
    • 感觉写的有点繁琐复杂,我在想如果不用drawRect方法,直接创建视图也是可以实现的。

    第二步:继承MAAnnotationView自定义一个大头针视图DDCustomAnnotationView

    DDCustomAnnotationView.h
    #import "DDCustomAnnotationView.h"
    
    #define kCalloutWidth       120.0
    #define kCalloutHeight      45.0
    
    @implementation DDCustomAnnotationView
    
    //复写父类init方法
    - (id)initWithAnnotation:(id<MAAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
    {
        if (self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier])
        {
            //创建大头针视图
            [self setUpClloutView];
        }
        return self;
    }
    
    - (void)setSelected:(BOOL)selected animated:(BOOL)animated
    {
        if (self.selected == selected)
        {
            return;
        }
        if (selected)
        {
            [self setUpClloutView];
        }
        else
        {
        }
        [super setSelected:selected animated:animated];
    }
    
    - (void)setUpClloutView {
        if (self.calloutView == nil)
        {
            self.calloutView = [[DDCustomCalloutView alloc] initWithFrame:CGRectMake(0, 0, kCalloutWidth, kCalloutHeight)];
            [self addSubview:self.calloutView];
        }
    }
    
    - (void)layoutSubviews {
        [super layoutSubviews];
        /*坐标不合适再此设置即可*/
        //Code ...
        self.calloutView.frame = CGRectMake(-(kCalloutWidth-self.frame.size.width)*0.5, -kCalloutHeight, kCalloutWidth, kCalloutHeight);
    }
    
    @end
    

    第三步:加载地图,添加大头针,显示气泡

    以下代码写的比较low,建议大家在使用高德地图的时候根据自己项目实际情况封装后再使用。

    ViewController.m中
    #import "ViewController.h"
    #import "Map.h"
    #import "DDPointAnnotation.h"
    
    @interface ViewController ()
    
    @property (nonatomic, strong) Map *map;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        //添加地图
        [self.view addSubview:self.map];
        //地图上添加大头针
        DDPointAnnotation *start = [[DDPointAnnotation alloc] init];
        start.title = @"国贸";
        start.number = @"1";
        start.image = [UIImage imageNamed:@"start"];
        start.coordinate = CLLocationCoordinate2DMake(39.90841537, 116.45969689);
        [self.map.mapView addAnnotation:start];
        
        DDPointAnnotation *end = [[DDPointAnnotation alloc] init];
        end.title = @"故宫";
        end.number = @"2";
        end.image = [UIImage imageNamed:@"end"];
        end.coordinate = CLLocationCoordinate2DMake(39.92000603, 116.39465332);
        [self.map.mapView addAnnotation:end];
        //设置地图范围
        [self.map.mapView showAnnotations:@[start,end] animated:NO];
    }
    
    - (Map *)map {
        if (!_map) {
            _map = [[Map alloc] initWithFrame:self.view.bounds];
        }
        return _map;
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    @end
    
    • 这里我们把地图放在了自己写了一个map类中,这个类是继承自UIView的,上面是个地图。里面实现了mapView的代理方法,添加了大头针视图和大头针气泡等设置。
    Map.h
    #import <UIKit/UIKit.h>
    #import <MAMapKit/MAMapKit.h>
    
    @interface Map : UIView
    
    @property (nonatomic, strong) MAMapView *mapView;//地图
    
    @end
    
    Map.m
    #import "Map.h"
    #import "DDPointAnnotation.h"
    #import "DDCustomAnnotationView.h"
    
    @interface Map ()<MAMapViewDelegate>
    
    @end
    
    @implementation Map
    
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
            
            self.backgroundColor = [UIColor clearColor];
            
            [self addSubview:self.mapView];
        }
        return self;
    }
    
    #pragma mark MAMapViewDelegate
    /**
     * @brief 根据anntation生成对应的View。
     * @param mapView 地图View
     * @param annotation 指定的标注
     * @return 生成的标注View
     */
    - (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id <MAAnnotation>)annotation {
        /*这里根据不同类型的大头针,生成不同的大头针视图
         为了方便起见我们继承MAPointAnnotation创建了自己的DDAnnotation,用来扩展更多属性,给大头针视图提供更多数据等
         */
        if ([annotation isKindOfClass:[DDPointAnnotation class]])
        {
            static NSString *reusedID = @"DDPointAnnotation_reusedID";
            DDCustomAnnotationView *annotationView = (DDCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reusedID];
            
            if (!annotationView) {
                annotationView = [[DDCustomAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reusedID];
                annotationView.canShowCallout = NO;//设置此属性为NO,防止点击的时候高德自带的气泡弹出
            }
            
            //给气泡赋值
            DDPointAnnotation *ddAnnotation = (DDPointAnnotation *)annotation;
            NSLog(@"********* %@ %@",ddAnnotation.title,ddAnnotation.number);
            annotationView.calloutView.textLabel.text = ddAnnotation.title;
            annotationView.calloutView.leftNumLabel.text = ddAnnotation.number;
            
            annotationView.image = ddAnnotation.image;//设置大头针图片
            annotationView.centerOffset = CGPointMake(0, -0.5*ddAnnotation.image.size.height);
    
            return annotationView;
        }
        
        return nil;
    }
    
    #pragma mark lazy load
    - (MAMapView *)mapView {
        if (!_mapView) {
            _mapView = [[MAMapView alloc] initWithFrame:self.bounds];
            _mapView.showsScale = NO;
            _mapView.showsCompass = NO;
            _mapView.rotateEnabled = NO;
            _mapView.delegate = self;
        }
        return _mapView;
    }
    
    @end
    
    • 这个可以根据自己项目的情况自己进行改写。

    三、总结

    • 自定义高德地图气泡大体思路就是这样,高德开放平台上的demo和这里相差无几。其中细节大家可做优化或者根据自己的项目需要做不同的封装。

    • 除此之外我还试过直接找到MAAnnotationView的位置,直接在地图上添加一个view的做法,也是可以实现的,看项目需要了。

    • 再此基础上做大头针数据的时时刷新等功能也是可以的,更多功能可以尽情扩展。

    四、demo

    实现效果比较粗糙,随便看看即可,更多功能还需自己扩展。运行demo的时候别忘了自己在AppDelegate里面设置高德AppKey。

    myDemo.png

    地址:https://github.com/Mexiang/New_Map_CallouView

    相关文章

      网友评论

      • 提点九路节度使:你好, 自定义大头针的图片可以使用网络图片吗? 我在使用网络图片的过程中,会出现图片重用的问题,你遇到过吗?
        追沐:@提点九路节度使 annotationView sd_ 直接用就可以了
        提点九路节度使:@追沐 你好,- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation 方法里,你是怎么用sdwebimage设置大头针图片呢?
        追沐:@提点九路节度使 可以是网络图片,直接用SDWebImage设置大头针的图片就可以了。你说的图片重用是?
      • 问夕阙:那个我第一次点击A大头针会触发 被点击的代理方法 我第二次再去点它 就不会触发方法,必须我先点击旁边其它的坐标大头针或者点下地图 ,再点击A 才会触发 ,这是为什么?
        问夕阙:@追沐 我写了个block传select属性 点击A坐标 A变成选中图片,再点击B坐标时,B没有变成选中图片
        问夕阙:@追沐 能加下我qq 526167726 私聊下吗
        追沐:if (self.selected == selected)
        {
        return;
        }
        如果处于点中状态的话,再次点击就不会执行点击方法。点击另一个大头针时,另一个成了选中状态,这个成了非选中状态。高德的SDK里面是这样的机制。

        这些点击方法都可以自定义,自己给calloutView写代理,回调方法里自己处理也可以的。不用高德的大头针点击方法代理。
      • 陌上北辰:大头针的image 如何自定义呢
        陌上北辰:@追沐 你的意思是咋这里面调用sdk的 image = ....; 我的意思是自定义imageView
        追沐:- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id <MAAnnotation>)annotation {}这个方法里,设置大头针图片。思路就是根据不同类型的大头针,添加图片。

      本文标题:自定义高德地图大头针气泡

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