随着移动互联网的发展,基于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已经修复。