1. 程式人生 > >ios 一步一步學會自定義地圖吹出框(CalloutView)-->(百度地圖,高德地圖,google地圖)...

ios 一步一步學會自定義地圖吹出框(CalloutView)-->(百度地圖,高德地圖,google地圖)...

前言

在ios上邊使用地相簿的同學肯定遇到過這樣的問題:吹出框只能設定title和subtitle和左右的view,不管是百度地圖還是高德地圖還是自帶的google地圖,只提供了這四個屬性,如果想新增更多的view,只能自定義。可是,類庫只能看到.h檔案,.m都看不到,這讓新手比較蛋疼,龐大的地圖類庫一時半會摸不著頭腦,從頭再學還需要時間,本文就教大家快速製作一個屬於自己的 CalloutView!等你一步一步調通後,再回過頭來使用系統自帶的方法設定callout,就會領悟這個過程。

正文

Xcode版本:4.6.1

SDK版本:6.0

百度地圖版本:1.2.2(關於地圖不必糾結,無論是百度還是高德還是google都是基於系統的MapKit,都是一樣的)

demo模式:非ARC,使用storyboard。

demo資源:

http://download.csdn.net/detail/mad1989/5252037

Step1

建立demo,並新增百度地圖的靜態類庫,helloword能顯示mapview

關於這一步我專門寫了教程,這裡就不再贅述,同樣,關於如何使用自帶的BMKPointAnnotation新增一個marker,我也不再說了,如果連這個你都不會,那麼先去官網看一下基本教程。

Step2

實現三個委託方法:

這個方法類似tableview新增cell,都是建立annotation

#pragma mark
#pragma mark - BMKMapview delegate
-(BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id<BMKAnnotation>)annotation;
這個方法在點選地圖marker時所觸發(並顯示callout)
-(void)mapView:(BMKMapView *)mapView didSelectAnnotationView:(BMKAnnotationView *)view;
這個方法在點選地圖任意位置,相當於隱藏callout
-(void)mapView:(BMKMapView *)mapView didDeselectAnnotationView:(BMKAnnotationView *)view;

原理:地圖上的marker是在viewForAnnoation裡建立的,同時也會隱含的為我們建立一個CalloutView,就是自帶的吹出框,只是我們看不到原始碼。其實這個吹出框(CalloutView)也是一個annotation,也會在viewForAnnotation裡被建立,他的座標應該和這個點的marker座標一樣,只要明白了這一點,就行了,marker和吹出框是兩個不同的annotation,他們有同樣的coordinate


Step3

自定義一個Annotation,為了簡單方便,我就直接繼承了mapview自帶的BMKPointAnnotation,這是一個經典的圖釘marker。


在這裡我添加了一個Dictionary屬性,目的是為了自定義的CalloutView吹出框顯示內容賦值,一會就明白了。

Step4

新增自定義Annotation到mapview

    //新增自定義Annotation
     CLLocationCoordinate2D center = {39.91669,116.39716};
    
    CustomPointAnnotation *pointAnnotation = [[CustomPointAnnotation alloc] init];
    pointAnnotation.title = @"我是中國人";//因為繼承了BMKPointAnnotation,所以這些title,subtitle都可以設定
    pointAnnotation.subtitle = @"我愛自己的祖國";
    
    pointAnnotation.coordinate = center;
    [mymapview addAnnotation:pointAnnotation];
    [pointAnnotation release];

在viewForanntion裡,由於我對marker沒太大要求,直接使用了BMKPinAnnotationView(圖釘),簡單設定image屬性為自己需要的圖示,如下所示:


展示一個效果圖:


顯然CalloutView只能設定title和subtitle,無法滿足我們的要求,那麼繼續下一步。

Step5

建立一個(自定義的CalloutView)的Annotation,相當於顯示calloutView的annotation。

[注意] 繼承自NSObject<BMKAnnotation>

CalloutMapAnnotation.h

#import <Foundation/Foundation.h>
#import "BMapKit.h"

@interface CalloutMapAnnotation : NSObject<BMKAnnotation>


@property (nonatomic) CLLocationDegrees latitude;
@property (nonatomic) CLLocationDegrees longitude;


@property(retain,nonatomic) NSDictionary *locationInfo;//callout吹出框要顯示的各資訊



- (id)initWithLatitude:(CLLocationDegrees)lat andLongitude:(CLLocationDegrees)lon;



@end

CalloutMapAnnotation.m
#import "CalloutMapAnnotation.h"

@implementation CalloutMapAnnotation


@synthesize latitude;
@synthesize longitude;
@synthesize locationInfo;

- (id)initWithLatitude:(CLLocationDegrees)lat
		  andLongitude:(CLLocationDegrees)lon {
	if (self = [super init]) {
		self.latitude = lat;
		self.longitude = lon;
	}
	return self;
}


-(CLLocationCoordinate2D)coordinate{

	CLLocationCoordinate2D coordinate;
	coordinate.latitude = self.latitude;
	coordinate.longitude = self.longitude;
	return coordinate;
    

}


@end

這裡設定了經緯度的屬性,和一個init初始化經緯度的方法(經緯度=marker的經緯度),同樣添加了一個Dictionary的屬性,為了傳遞在CalloutView上內容的賦值,繼續。

Step6

這一步我們建立自定義的View,想要什麼佈局就寫什麼樣的佈局,想要多少屬性就加多少屬性,這裡我使用了code方式畫了一個contentView,裡面的子view使用Xib方式建立。

[注意:繼承自BMKAnnotationView]

CallOutAnnotationView.h

#import "BMKAnnotationView.h"
#import "BusPointCell.h"

@interface CallOutAnnotationView : BMKAnnotationView


@property(nonatomic,retain) UIView *contentView;

//新增一個UIView
@property(nonatomic,retain) BusPointCell *busInfoView;//在建立calloutView Annotation時,把contentView add的 subview賦值給businfoView


@end

BusPointCell是ContentView裡的subview,這個view就是顯示各個元件,並賦不同的值

CallOutAnnotationView.m

#import "CallOutAnnotationView.h"
#import <QuartzCore/QuartzCore.h>


#define  Arror_height 6

@implementation CallOutAnnotationView
@synthesize contentView;
@synthesize busInfoView;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
    }
    return self;
}

-(void)dealloc{
    [contentView release];
    [busInfoView release];
    [super dealloc];
}

-(id)initWithAnnotation:(id<BMKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier{

    
    self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.canShowCallout = NO;
        self.centerOffset = CGPointMake(0, -55);
        self.frame = CGRectMake(0, 0, 240, 80);

        UIView *_contentView = [[UIView alloc] initWithFrame:CGRectMake(5, 5, self.frame.size.width-15, self.frame.size.height-15)];
        _contentView.backgroundColor   = [UIColor clearColor];
        [self addSubview:_contentView];
        self.contentView = _contentView;
        [_contentView release];
    }
    return self;

}

-(void)drawRect:(CGRect)rect{

    [self drawInContext:UIGraphicsGetCurrentContext()];
    
    self.layer.shadowColor = [[UIColor blackColor] CGColor];
    self.layer.shadowOpacity = 1.0;
    self.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
    

}

-(void)drawInContext:(CGContextRef)context
{
	
    CGContextSetLineWidth(context, 2.0);
    CGContextSetFillColorWithColor(context, [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0].CGColor);
    
    [self getDrawPath:context];
    CGContextFillPath(context);
    
}
- (void)getDrawPath:(CGContextRef)context
{
    CGRect rrect = self.bounds;
	CGFloat radius = 6.0;
    
	CGFloat minx = CGRectGetMinX(rrect),
    midx = CGRectGetMidX(rrect),
    maxx = CGRectGetMaxX(rrect);
	CGFloat miny = CGRectGetMinY(rrect),
    // midy = CGRectGetMidY(rrect),
    maxy = CGRectGetMaxY(rrect)-Arror_height;
    CGContextMoveToPoint(context, midx+Arror_height, maxy);
    CGContextAddLineToPoint(context,midx, maxy+Arror_height);
    CGContextAddLineToPoint(context,midx-Arror_height, maxy);
    
    CGContextAddArcToPoint(context, minx, maxy, minx, miny, radius);
    CGContextAddArcToPoint(context, minx, minx, maxx, miny, radius);
    CGContextAddArcToPoint(context, maxx, miny, maxx, maxx, radius);
    CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
    CGContextClosePath(context);
}



@end

BusPointCell.h

想要多少label,就可以有多少label

#import <UIKit/UIKit.h>

@interface BusPointCell : UIView
@property (retain, nonatomic) IBOutlet UILabel *aliasLabel;
@property (retain, nonatomic) IBOutlet UILabel *speedLabel;
@property (retain, nonatomic) IBOutlet UILabel *degreeLabel;
@property (retain, nonatomic) IBOutlet UILabel *nameLabel;

@end

BusPointCell.m
#import "BusPointCell.h"

@implementation BusPointCell

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

    }
    return self;
}
- (void)dealloc {
    [_aliasLabel release];
    [_speedLabel release];
    [_degreeLabel release];
    [_nameLabel release];
    [super dealloc];
}
@end

BusPointCell.xib


Step7

自定義的CalloutView都準備妥當,現在就是要實現他們的部分了,簡單說一下原理,在didSelectAnnotationView函式裡建立並新增calloutview的annotation(CalloutMapAnnotation),然後在viewForAnnotation函式內例項化要顯示的calloutview(CallOutAnnotationView)

首先宣告一個區域性變數CalloutMapAnnotation *_calloutMapAnnotation;

在didSelectAnnotationView函式內新增如下程式碼:

    //CustomPointAnnotation 是自定義的marker標註點,通過這個來得到新增marker時設定的pointCalloutInfo屬性
    CustomPointAnnotation *annn = (CustomPointAnnotation*)view.annotation;
    
    
    if ([view.annotation isKindOfClass:[CustomPointAnnotation class]]) {
        
        //如果點到了這個marker點,什麼也不做
        if (_calloutMapAnnotation.coordinate.latitude == view.annotation.coordinate.latitude&&
            _calloutMapAnnotation.coordinate.longitude == view.annotation.coordinate.longitude) {
            return;
        }
        //如果當前顯示著calloutview,又觸發了select方法,刪除這個calloutview annotation
        if (_calloutMapAnnotation) {
            [mapView removeAnnotation:_calloutMapAnnotation];
            _calloutMapAnnotation=nil;
            
        }
        //建立搭載自定義calloutview的annotation
        _calloutMapAnnotation = [[[CalloutMapAnnotation alloc] initWithLatitude:view.annotation.coordinate.latitude andLongitude:view.annotation.coordinate.longitude] autorelease];
        
        //把通過marker(ZNBCPointAnnotation)設定的pointCalloutInfo資訊賦值給CalloutMapAnnotation
        _calloutMapAnnotation.locationInfo = annn.pointCalloutInfo;
        
        [mapView addAnnotation:_calloutMapAnnotation];

        
        
        [mapView setCenterCoordinate:view.annotation.coordinate animated:YES];
        
    }


其次,要在viewForAnnotation裡建立我們的calloutview(CallOutAnnotationView),新增如下程式碼:

    else if ([annotation isKindOfClass:[CalloutMapAnnotation class]]){
        
        //此時annotation就是我們calloutview的annotation
        CalloutMapAnnotation *ann = (CalloutMapAnnotation*)annotation;
        
        //如果可以重用
        CallOutAnnotationView *calloutannotationview = (CallOutAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"calloutview"];
        
        //否則建立新的calloutView
        if (!calloutannotationview) {
            calloutannotationview = [[[CallOutAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"calloutview"] autorelease];

            BusPointCell *cell = [[[NSBundle mainBundle] loadNibNamed:@"BusPointCell" owner:self options:nil] objectAtIndex:0];
            
            [calloutannotationview.contentView addSubview:cell];
            calloutannotationview.busInfoView = cell;
        }
        
        //開始設定新增marker時的賦值
        calloutannotationview.busInfoView.aliasLabel.text = [ann.locationInfo objectForKey:@"alias"];
        calloutannotationview.busInfoView.speedLabel.text = [ann.locationInfo objectForKey:@"speed"];
        calloutannotationview.busInfoView.degreeLabel.text =[ann.locationInfo objectForKey:@"degree"];
        calloutannotationview.busInfoView.nameLabel.text =  [ann.locationInfo objectForKey:@"name"];
        
        return calloutannotationview;
        
    }

[注意]在新增marker的判斷裡一定要設定markerannotation.canShowCallout =NO; 否則點選marker會預設顯示系統的吹出框

Step8

calloutview的annotation也建立和添加了,接下來我們就設定一下marker對應吹出框的資料:


然後執行一下:


哈哈!搞定了吧,具體佈局可以自己通過code方式,或xib方式設計,目前點選marker能顯示了,可是點選其它區域還是無法顯示,所以我們在didDeselectAnnotationView方法裡還需要判斷一下,remove掉。

-(void)mapView:(BMKMapView *)mapView didDeselectAnnotationView:(BMKAnnotationView *)view{
    
    if (_calloutMapAnnotation&&![view isKindOfClass:[CallOutAnnotationView class]]) {

        if (_calloutMapAnnotation.coordinate.latitude == view.annotation.coordinate.latitude&&
            _calloutMapAnnotation.coordinate.longitude == view.annotation.coordinate.longitude) {
            [mapView removeAnnotation:_calloutMapAnnotation];
            _calloutMapAnnotation = nil;
        }
        
        
    }

}


最後

之所以在顯示marker的annotation[本文為CustomPointAnnotation]和顯示calloutview的annotation[本文為CalloutMapAnnotation]裡各新增一個Dictionary,就是要在點選時通過marker傳遞資料,新增時通過calloutview的annotation例項來設定每一個屬性的資料,已達到不同的maker,顯示不同的資料。

可能我的過程不是太清晰,自己仔細研究一下這三個函式和mapview自帶的callout呼叫過程,便會明白。

2012年4月12日