ios 一步一步學會自定義地圖吹出框(CalloutView)-->(百度地圖,高德地圖,google地圖)
高德地圖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。
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;
- -(void)mapView:(BMKMapView *)mapView didSelectAnnotationView:(BMKAnnotationView *)view;
- -(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調用過程,便會明白。
dmeo地址:http://download.csdn.net/detail/mad1989/5252037
2012年4月12日