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
這個方法在點選地圖marker時所觸發(並顯示callout)#pragma mark #pragma mark - BMKMapview delegate -(BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id<BMKAnnotation>)annotation;
-(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日