地图展示自定义视图

随着移动互联网的发展,基于LBS的功能开发需求越来越多。但是做好LBS功能却很不容易,现在国内大部分的App都是使用高德或者百度的SDK,也有使用MapKit.framework的,这三种我都有过尝试,其中高德地图用的最少,百度地图用的最多。刚开始的时候使用的是原生的,但是由于项目的需求,需要获取一些poi数据之类的,公司要求使用百度地图的SDK,在使用的过程中发现百度地图SDK的性能是比较差的,加载速度和内存占用方面都表现的比较差。 但是随着版本的更新,也变得越来越好, 虽然在响应速度与稳定性与原生的还是有差距,但是百度地图SDK具有良好的注释及Demo。所以本文是基于Baidu地图SDK来说明地图如何自定义视图。


我们的目标是要实现下图的效果:

                     默认显示点击头像显示


要想实现上述的效果,首先要了解地图自定义视图从创建到显示到地图的过程。 流程大体如下:

1、添加Annotation至BMKMapView

2、调用BMKMapView的Delegate方法,根据Annotation返回自定义View。


废话不多说,直接上代码,首先我们创建一个继承自BMKPointAnnotation的类QFBFHAnnotation, 增加一个成员变量:

#import "BMKPointAnnotation.h"
#import "LXBroker.h"

@interface QFBFHAnnotation : BMKPointAnnotation

//自定义的经纪人Model
@property (strong, nonatomic) LXBroker * broker;

@end


然后我们创建一个自定义的BMKAnnotationView的视图

地图上自定义视图一般继承自BMKAnnotationView,这个类提供一些重用的方法及其他自定义方法。 在这里我没有用到,有兴趣的可以去了解下。

自定义BMKAnnotationView一般使用初始化方法:

- (id)initWithAnnotation:(id <BMKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
    
    self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
    if (self) {
        
        self.backgroundColor = [UIColor clearColor];
        
        //是否显示选项条视图
        _isShowOptionView = NO;
        
        //设定UI界面
        [self setupUI];
    }
    
    return self;
}

设定UI、添加头像按钮和选项条视图、选项按钮等:

- (void) setupUI {
    
    //获取对应的Annotation
    QFBFHAnnotation * curAnnotation = (QFBFHAnnotation *)self.annotation;
    
    self.headerButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 55, 55)];
    [self.headerButton setBackgroundColor:[UIColor colorWithRed:253.0 / 255.0 green:212.0 / 255.0 blue:11.0 / 255.0 alpha:0.8]];
    
    //头像设为圆形及白色的边框
    self.headerButton.layer.cornerRadius = self.headerButton.frame.size.width / 2.0;
    self.headerButton.layer.borderColor = [UIColor whiteColor].CGColor;
    self.headerButton.layer.borderWidth = 2.0;
    self.headerButton.clipsToBounds = YES;
    
    [self.headerButton setBackgroundImage:curAnnotation.broker.headerImage forState:UIControlStateNormal];
    [self.headerButton addTarget:self action:@selector(headerButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:self.headerButton];
    
    //选项条气泡背景
    UIImage * bubbleImage = [self  buffetFindHouseBubbleImage];
    self.optionView = [[UIImageView alloc] initWithFrame:[self optionViewDefaultFrame]];
    self.optionView.userInteractionEnabled = YES;
    self.optionView.clipsToBounds = YES;
    self.optionView.image = bubbleImage;
    self.optionView.backgroundColor = [UIColor clearColor];
    
    //添加电话/短信/更多 按钮
    NSArray * imageNames = @[@"BFH_tel", @"BFH_SMS", @"BFH_more_tag"];
    CGFloat buttonY = 6;
    CGFloat buttonWidth = CGRectGetWidth(self.optionView.frame) / imageNames.count;
    CGFloat buttonHeight = CGRectGetHeight(self.optionView.frame) - buttonY;
    for (int i = 0; i < imageNames.count; i ++) {
        
        UIButton * button = [[UIButton alloc] initWithFrame:CGRectMake(buttonWidth * i, buttonY, buttonWidth, buttonHeight)];
        UIImage * image = [UIImage imageNamed:imageNames[i]];
        [button setImage:image forState:UIControlStateNormal];
        button.tag = i;
        [button addTarget:self action:@selector(optionButtonClick:) forControlEvents:UIControlEventTouchUpInside];
        
        [self.optionView addSubview:button];
        
    }
    
    //添加按钮之间的分割线
    for (int i = 1; i < imageNames.count; i ++) {
        
        UIImageView * sepImageView = [[UIImageView alloc] initWithFrame:CGRectMake(buttonWidth * i , buttonY, 1, buttonHeight)];
        sepImageView.image = [UIImage imageNamed:@"BFH_vertical_sepline"];
        [self.optionView addSubview:sepImageView];
    }

    //默认为不显示选项条视图
    self.optionView.hidden = YES;
    self.optionView.frame = [self optionViewHiddenFrame];

    [self addSubview:self.optionView];
}

自定义视图的UI部分基本完成, 但是当选项条视图隐藏的时候,点击选项条视图显示所在的区域,地图是无法获取点击事件的。如图所示:


要使得紫色的区域,地图可以响应点击事件,我们可以做如下处理:

//点击空白位置时不处理事件。
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    BOOL isInHeaderButton = !self.headerButton.hidden && self.headerButton.userInteractionEnabled && [self.headerButton pointInside:[self convertPoint:point toView:self.headerButton] withEvent:event];

    if (self.isShowOptionView) {
        
        BOOL isInOptionView = !self.optionView.hidden && self.optionView.userInteractionEnabled && [self.optionView pointInside:[self convertPoint:point toView:self.optionView] withEvent:event];
        
        //当显示OptionView的时候,只有headerButton和optionView能响应点击事件。
        if (isInHeaderButton || isInOptionView)
        {
            return YES;
        }
        return NO;
    }
    else {
        
        //当不显示OptionView的时候,只有headerButton能响应点击事件。
        if (isInHeaderButton)
        {
            return YES;
        }
        return NO;
        
    }
}

当然后面的还有各按钮点击事件的处理, 我们可以使用Delegate,也可以使用block, 我这里使用的是block,先创建两个Block成员变量:

@property (copy, nonatomic) void(^headerButtonClickBlock)(QFBFHAnnotationView * annotationView,UIButton * headerButton);
@property (copy, nonatomic) void(^optionButtonClickBlock)(QFBFHAnnotationView * annotationView,UIButton * optionButton, NSInteger index);

在创建两个方法:

//点击头像后的block回调
- (void) handleAnnotationViewHeaderButtonClick:(void(^)(QFBFHAnnotationView * annotationView, UIButton * HeaderButton))block;

//点击选项条视图上按钮的block回调
- (void) handleAnnotationViewOptionButtonClick:(void(^)(QFBFHAnnotationView * annotationView, UIButton * optionButton, NSInteger index))block;

- (void) handleAnnotationViewHeaderButtonClick:(void (^)(QFBFHAnnotationView *, UIButton *button))block {
    
    self.headerButtonClickBlock = block;
}

- (void) handleAnnotationViewOptionButtonClick:(void(^)(QFBFHAnnotationView * annotationView, UIButton * optionButton, NSInteger index))block;{
    
    self.optionButtonClickBlock = block;
    
}

按钮点击后,回调:

- (void) optionButtonClick: (UIButton *)sender {
    
    //点击后将self放于父视图最前。
    [self.superview bringSubviewToFront:self];
    
    if (self.optionButtonClickBlock) {
        self.optionButtonClickBlock(self, sender, sender.tag);
    }
    
}

- (void) headerButtonClick:(UIButton *)sender {
    
    [self.superview bringSubviewToFront:self];

    if (self.headerButtonClickBlock) {
        self.headerButtonClickBlock(self, sender);
    }
    
}

到这里,自定义视图基本完成,更多细节可以下载下面的Demo了解。


前期准备工作基本完成,我们要在地图上显示自定以的视图,首先要添加一个Annotation

地图上增加一个自定义的Annotation:

 //不设定delegate无法调用delegate方法生成自定义视图,生成的是一个默认的大头针
    self.mapView.delegate = self;
    
    LXBroker *broker = [[LXBroker alloc] init];
    broker.headerImage = [UIImage imageNamed:@"test"];
    broker.telPhone = @"13348782277";
    broker.name = @"李新星";
    broker.coordinate2D = CLLocationCoordinate2DMake(22.646582, 114.034055);
    
    //在地图的中心位置上添加一个annotation
    QFBFHAnnotation * annotation = [[QFBFHAnnotation alloc] init];
    annotation.broker = broker;
    //设定自定义视图显示在地图上的位置
    annotation.coordinate = broker.coordinate2D;
    [self.mapView addAnnotation:annotation];
    
    //设定地图的中心位置
    self.mapView.centerCoordinate = broker.coordinate2D;

当执行完  [self.mapView addAnnotation:annotation]   后,会回调Delegate方法,返回我们想显示在地图上的自定义视图。代码如下:

- (BMKAnnotationView *)mapView:(BMKMapView *)view viewForAnnotation:(id <BMKAnnotation>)annotation
{
    
    if ([annotation isKindOfClass:[QFBFHAnnotation  class]]) {
        // 生成重用标示identifier
        static NSString * QFAnnotationViewID = @"QFBFHAnnotationView";
        
        // 检查是否有重用的缓存
        QFBFHAnnotationView * annotationView = (QFBFHAnnotationView *)[view dequeueReusableAnnotationViewWithIdentifier:QFAnnotationViewID];
        
        if (!annotationView) {
            annotationView = [[QFBFHAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:QFAnnotationViewID];
        }
        
        annotationView.annotation = annotation;
        
        //不弹出系统默认的气泡
        annotationView.canShowCallout = NO;
        
        //设为可以响应点击事件
        annotationView.enabled = YES;
        
        CGRect annotationViewFrame = [QFBFHAnnotationView defaultBounds];
        //放置在所在位置的正上方。
        annotationViewFrame.origin.x -= annotationViewFrame.size.width / 2;
        annotationViewFrame.origin.y -= annotationViewFrame.size.height / 2;
        annotationView.frame = annotationViewFrame;
        
        // 设置是否可以拖拽
        annotationView.draggable = NO;
        
        annotationView.isShowOptionView = NO;
        
        __block ViewController * weakSelf = self;
        
        [annotationView handleAnnotationViewHeaderButtonClick:^(QFBFHAnnotationView *clickedAnnotationView, UIButton *HeaderButton) {
            
            //收起其他的annotation
            NSArray* array = [NSArray arrayWithArray:weakSelf.mapView.annotations];
            
            if (array.count) {
                for (int i = 0; i < array.count; i ++) {
                    
                    QFBFHAnnotation * oldAnnotaion = (QFBFHAnnotation *)array[i];
                    if ([oldAnnotaion isKindOfClass:[QFBFHAnnotation class]] && [weakSelf.mapView viewForAnnotation:oldAnnotaion] != clickedAnnotationView)
                    {
                        
                        QFBFHAnnotationView * oldAnnotationView = (QFBFHAnnotationView *)[weakSelf.mapView viewForAnnotation:oldAnnotaion];
                        oldAnnotationView.isShowOptionView = NO;
                        
                    }
                }
            }
            
            clickedAnnotationView.isShowOptionView = !clickedAnnotationView.isShowOptionView;
            
        }];
        
        //点击选项条上按钮的回调
        [annotationView handleAnnotationViewOptionButtonClick:^(QFBFHAnnotationView *annotationView, UIButton *optionButton, NSInteger index) {
            
            QFBFHAnnotation * curAnnotation =  (QFBFHAnnotation *)annotationView.annotation;
            LXBroker * broker = curAnnotation.broker;
            
            NSLog(@"index is %d, broker is %@", index, broker);
        }];
        
        return annotationView;
        
    }
    else {
        
        return nil;
        
    }
    
}

到这里终于实现了我们想要的自定义视图效果, 但是发现视图显示的时候是突然显示,非常生硬,我们可以加入动画效果。 mapView在执行Delegate方法返回自定义视图后,还会执行另外一个delegate方法:

- (void)mapView:(BMKMapView *)mapView didAddAnnotationViews:(NSArray *)views;

在这个方法里面我们可以给视图加一个显示的动画效果:

- (void)mapView:(BMKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
    
    for (UIView *view in views) {
        if ([view isKindOfClass:[QFBFHAnnotationView class]]) {
            //增加一个显示的动画效果
            [self addBounceAnnimationToView:view];
        }
    }
    
}

#pragma mark -  添加bounce类型的显示动画效果。
- (void)addBounceAnnimationToView:(UIView *)view
{
    CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
    
    bounceAnimation.values = @[@(0.05), @(1.1), @(0.9), @(1)];
    
    bounceAnimation.duration = 0.6;
    NSMutableArray *timingFunctions = [[NSMutableArray alloc] init];
    for (NSInteger i = 0; i < 4; i++) {
        [timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    }
    [bounceAnimation setTimingFunctions:timingFunctions.copy];
    bounceAnimation.removedOnCompletion = NO;
    
    [view.layer addAnimation:bounceAnimation forKey:@"bounce"];
}

至此,终于大功告成,更多细节,大家可以下载下面的Demo去了解。


注:本文中百度地图SDK的版本为2.4.1, 在iPhone6+上有一个显示bug。 百度地图SDK2.5.0已经修复。


下载Demo请点击这里


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值