定位和地图

为了使用iOS中的地图和定位功能,我们需要使用Core Location和Map Kit,分别用于地理定位和地图展示。

1.Core Location

1.获取权限

要开始定位服务的第一步就是获取用户权限,在iOS8之前,获取位置服务权限是隐式的。你只要初始化CLLocationManager并设置它的delegate为它的owner,当你开始调用startUpdatingLocation进行定位时,如果应用程序还没有被许可或者之前被拒绝了的话,会触发系统弹出提示框向用户获取位置服务的授权:

@interface ViewController ()<CLLocationManagerDelegate>{
    CLLocationManager *locationmanager;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    [self.locationManager startUpdatingLocation];
}

即让 CLLocationManager 取得最新的位置的这个操作会让系统弹出是否允许位置服务的提示。

在 iOS 8,取得权限和使用位置服务分成两个动作了。分别用两个不同的方法取得权限:requestWhenInUseAuthorization 和 requestAlwaysAuthorization。前者只能让应用在使用的时候有权获取位置数据;后者会得到跟上面讲的一样获得后台位置服务。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    if (IS_IOS8) {
        locationmanager = [[CLLocationManager alloc] init];
        [locationmanager requestAlwaysAuthorization];
        [locationmanager requestWhenInUseAuthorization];
        locationmanager.delegate = self;
    }    
}

注意你需要判断设备是不是iOS8才可以使用上面的方法,否则locationmanager不响应相应的方法会造成应用奔溃:

#define IS_IOS8 ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8)

或者也可以这样:

if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
        [self.locationManager requestWhenInUseAuthorization];
    }

在iOS8之前,你可以选择性在 Info.plist 中包含 'NSLocationUsageDescription' 的关键字。这个值是一个纯文本的字符串,向用户说明了应用要使用位置服务的目的。现在这个值被拆分成了两个不同的关键字:NSLocationWhenInUseUsageDescription和 NSLocationAlwaysUsageDescription,而且是必填的;如果你不添加对应的关键字就去调用 requestWhenInUseAuthorization或requestAlwaysAuthorization,那么将不会有任何的弹出提示给用户。

那么下面这样设置是对的吗?

注意不能像上图那样设置为两个BOOL值,那样会导致用户在隐私设置页面打开你的应用的定位服务设置后使得设置页面奔溃。因为上面两个关键字对于的必须是字符串,不能是bool值。

下面这样才是正确的(我以前就在这里被其它文章误导所以使用bool值)

注意授权弹出框会只显示一次。在CLLocationManager.authorizationStatus()返回除NotDetermined之外的值之后,不管调用requestWhenInUseAuthorization()或requestAlwaysAuthorization()都不会有一个 UIAlertController 显示出来了。在用户最初的选择之后,唯一改变授权的方式是到隐私设置中设置。

为了快速到达隐私设置页面,Apple引入UIApplicationOpenSettingsURLString,它存储了一个 URL 用来打开当前应用在 Settings.app 对应的页面。如下:

- (IBAction)setting:(id)sender {
    if (&UIApplicationOpenSettingsURLString != NULL) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
    }
}

2.CLLocationManager

如上讲的,CLLocationManager是管理我们的定位服务的,CLLocationManager包括下列属性:

desiredAccuracy是指定位精度,它是个枚举类型:

kCLLocationAccuracyBest:最精确定位
CLLocationAccuracy kCLLocationAccuracyNearestTenMeters:十米误差范围
kCLLocationAccuracyHundredMeters:百米误差范围
kCLLocationAccuracyKilometer:千米误差范围
kCLLocationAccuracyThreeKilometers:三千米误差范围

distanceFilter是位置信息更新最小距离,只有移动大于这个距离才更新位置信息,默认为kCLDistanceFilterNone:不进行距离限制。

stopUpdatingLocation:停止定位追踪
startUpdatingHeading:开始导航方向追踪
stopUpdatingHeading:停止导航方向追踪
startMonitoringForRegion::开始对某个区域进行定位追踪,开始对某个区域进行定位后。如果用户进入或者走出某个区域会调用

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region

- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region

stopMonitoringForRegion:停止对某个区域进行定位追踪
requestWhenInUseAuthorization:请求获得应用使用时的定位服务授权
requestAlwaysAuthorization:请求获得应用一直使用定位服务授权,

我们从一个例子开始讲起,先完成一些需要的工作:

记住,你需要把MapKit framework加入到项目中. (Control + Click Frameworks folder -> Add -> Existing Frameworks)

为了分离控制器,我们新建一个NSObject子类,

MyLocation.h

#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>

@interface MyLocation : NSObject<CLLocationManagerDelegate,UIAlertViewDelegate>

@end

MyLocation.m

-(void)startLocation
{
    if([CLLocationManager locationServicesEnabled] && [CLLocationManager authorizationStatus] != kCLAuthorizationStatusDenied) {
        _manager=[[CLLocationManager alloc]init];
        _manager.delegate=self;
        _manager.desiredAccuracy = kCLLocationAccuracyBest;
        _manager.distanceFilter=100;
        [_manager requestAlwaysAuthorization];
        [_manager startUpdatingLocation];
    }
    else {
        UIAlertView *alvertView=[[UIAlertView alloc]initWithTitle:@"提示" message:@"你还未开启定位服务" delegate:nil cancelButtonTitle:@"设置" otherButtonTitles: @"取消", nil];
        alvertView.delegate = self;
        [alvertView show];
    }    
}

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    if (&UIApplicationOpenSettingsURLString != NULL) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
    }
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
    [self stopLocation];
}

-(void)stopLocation{
    _manager = nil;
}

需要注意几点:

1).定位频率和定位精度并不是越精确越好,需要视实际情况而定,因为越精确越耗性能,也就越费电。

2).定位成功后会根据设置情况频繁调用

 -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations

方法,这个方法返回一组地理位置对象数组,每个元素一个CLLocation代表地理位置信息(包含经度、纬度、海报、行走速度等信息),之所以返回数组是因为有些时候一个位置点可能包含多个位置。

3).使用完定位服务后如果不需要实时监控应该立即关闭定位服务以节省资源。

4).除了提供定位功能,CLLocationManager还可以调用startMonitoringForRegion:方法对指定区域进行监控。

3.实例

对于CLLocation对象,适合使用单例来创建它

+ (MyLocation *)shareLocation{
    static dispatch_once_t pred = 0;
    __strong static id _sharedObject = nil;
    dispatch_once(&pred, ^{
        _sharedObject = [[self alloc] init];
    });
    return _sharedObject;
}

以下协议会在startUpdatingLocation后被调用,我们可以在这个方法中得到我们需要的信息:

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    CLLocation *loc = [locations firstObject];
    self.lastCoordinate = loc.coordinate;
}

我们通过Block传递消息到控制类:

typedef void (^locationCorrrdinate)(CLLocationCoordinate2D locationCoordinate);
...省略
-(void)getLocationMessageCoordinate2D:(locationCorrrdinate)locationCoordinate{
    [self startLocation];
    locationCoordinate(self.lastCoordinate);
}

ViewController.m

- (IBAction)getLocationMessage:(UIButton *)sender {
    __block __weak ViewController *wself = self;
    
    [[MyLocation shareLocation]getLocationMessageCoordinate2D:^(CLLocationCoordinate2D locationCoordinate) {
        NSLog(@"纬度--%f 经度----%f",locationCoordinate.latitude,locationCoordinate.longitude);
        NSString  *str = [NSString stringWithFormat:@"%f,%f",locationCoordinate.latitude,locationCoordinate.longitude];
        wself.coordinateLabel.text = str;
    }];
}

现在我们运行应用,现在你按下location,你可以看到:

正如我们所期望的,如果你点击不允许之后,继续点击location按钮,你会看到:

点击设置选项可以快速跳转到地理位置服务设置页面。

不过有一点问题,在我们允许使用地理位置服务后,最初我们按下按钮的时候,显示为0,再按下才有:

2015-03-16 22:19:06.528 LocationDemo[2420:1034611] 纬度--0.000000 经度----0.000000
2015-03-16 22:19:09.445 LocationDemo[2420:1034611] 纬度--23.046266 经度----113.386919

因为设备获取经纬度需要一定的时间,最初按下的时候还没有获取到经纬度值。下面的代码是解决办法。

@property (nonatomic,strong)locationCorrrdinate locationCoordinateBlock;
...省略
-(void)getLocationMessageCoordinate2D:(locationCorrrdinate)locationCoordinate{
    self.locationCoordinateBlock = [locationCoordinate copy];
    [self startLocation];
}

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    CLLocation *loc = [locations firstObject];
    self.lastCoordinate = loc.coordinate;
    self.locationCoordinateBlock(loc.coordinate);
}

注意,我们之所以需要[locationCoordinate copy]是因为locationCoordinate是一个block,分配在栈上,超过它的作用域之后block就不在了。所以必须先copy到heap上,这样就可以在之后的使用中正常访问。我在这篇文章中曾详细讲过。

现在运行一切正常。

3.地理位置编码

为了将你上面得到的纬度和经度显示为真实的地址,你需要使用地理位置编码。
使用CLGeocoder可以完成“地理编码”和“反地理编码”
地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
反地理编码:根据给定的经纬度,获得具体的位置信息

地理编码方法

- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler; 

反地理编码方法

- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;

我们先看反地理编码方法,代码如下:

-(void)getLocationMessageCoordinate2D:(locationCorrrdinate)locationCoordinate address:(address)address detailAddress:(address)detailAddress{
    self.locationCoordinateBlock = [locationCoordinate copy];
    self.addressBlock = [address copy];
    self.detailAddressBlock = [detailAddress copy];
    [self startLocation];
}

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    CLLocation *loc = [locations firstObject];
    self.coordinate = loc.coordinate;
    self.locationCoordinateBlock(loc.coordinate);
    
    CLGeocoder *geocoder = [[CLGeocoder alloc]init];

    [geocoder reverseGeocodeLocation:loc completionHandler:^(NSArray *placemarks, NSError *error) {
        if (placemarks.count > 0) {
            CLPlacemark *placemark = [placemarks objectAtIndex:0];
            _address = [NSString stringWithFormat:@"%@%@",placemark.administrativeArea,placemark.locality];
            _detailAddress = [NSString stringWithFormat:@"%@",placemark.name];//详细地址
            
             //        NSString *name=placemark.name;//地名
             //        NSString *thoroughfare=placemark.thoroughfare;//街道
             //        NSString *subThoroughfare=placemark.subThoroughfare; //街道相关信息,例如门牌等
             //        NSString *locality=placemark.locality; // 城市
             //        NSString *subLocality=placemark.subLocality; // 城市相关信息,例如标志性建筑
             //        NSString *administrativeArea=placemark.administrativeArea; // 州
             //        NSString *subAdministrativeArea=placemark.subAdministrativeArea; //其他行政区域信息
             //        NSString *postalCode=placemark.postalCode; //邮编
             //        NSString *ISOcountryCode=placemark.ISOcountryCode; //国家编码
             //        NSString *country=placemark.country; //国家
             //        NSString *inlandWater=placemark.inlandWater; //水源、湖泊
             //        NSString *ocean=placemark.ocean; // 海洋
             //        NSArray *areasOfInterest=placemark.areasOfInterest; //关联的或利益相关的地标
        }
        if (self.addressBlock) {
            self.addressBlock(_address);
        }
        if (self.detailAddressBlock) {
            self.detailAddressBlock(_detailAddress);
        }
    }];
}

ViewController.m

- (IBAction)getAddress:(UIButton *)sender {
    __block __weak ViewController *wself = self;
    [[MyLocation shareLocation]getLocationMessageCoordinate2D:^(CLLocationCoordinate2D locationCoordinate) {
    } address:^(NSString *string) {
        wself.addressLabel.text = string;
    } detailAddress:^(NSString *string) {
    }];
}

- (IBAction)getDetailAddress:(UIButton *)sender {
    __block __weak ViewController *wself = self;
    [[MyLocation shareLocation]getLocationMessageCoordinate2D:^(CLLocationCoordinate2D locationCoordinate) {
        //
    } address:^(NSString *string) {
        //
    } detailAddress:^(NSString *string) {
        wself.detailAddressLabel.text = string;
    }];
}

好了,运行效果如下:

看一些地理编码:根据给定的地名,获得具体的位置信息,如下:

-(void)geocodeAddress{
    CLGeocoder *geocoder = [[CLGeocoder alloc]init];
    [geocoder geocodeAddressString:@"广东工业大学" completionHandler:^(NSArray *placemarks, NSError *error) {
        
        if (placemarks.count>0) {
            CLPlacemark *placemark=[placemarks firstObject];
            
            CLLocation *location=placemark.location;//位置
            CLRegion *region=placemark.region;//区域
            NSLog(@"位置:%@,区域:%@",location,region);
        }
    }];
}

打印结果如下;

2015-03-17 13:19:02.179 LocationDemo[2755:1160527] 位置:<+23.13312700,+113.29970500> +/- 100.00m (speed -1.00 mps / course -1.00) @ 15/3/17 中国标准时间下午1:19:02,区域:CLCircularRegion (identifier:'<+23.13312650,-66.70029500> radius 14867919.48', center:<+23.13312650,-66.70029500>, radius:14867919.48m)

2.MapKit

1.显示我们所在的位置

上文我们已经可以查看我们的地理位置了,现在我们需要在地图上显示我们所处的位置。如下我们在地图中显示我们所处的区域。

- (IBAction)getDetailAddress:(UIButton *)sender {
    __block __weak ViewController *wself = self;
    [[MyLocation shareLocation]getLocationMessageCoordinate2D:^(CLLocationCoordinate2D locationCoordinate) {
        MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(locationCoordinate, 2000, 2000);
        [self.mapView setRegion:region];

或:

MKCoordinateSpan span=MKCoordinateSpanMake(1, 1);
MKCoordinateRegion region=MKCoordinateRegionMake(locationCoordinate, span);
[_mapView setRegion:region animated:true];

然后我们地图中显示我们所处的位置,并添加一个大头针,大头针的位置是我们前文所获取到的地理位置。

_mapView.userTrackingMode=MKUserTrackingModeFollow;       
_mapView.mapType=MKMapTypeStandard;
//添加大头针
MyAnnotation *annotation=[[MyAnnotation alloc]init];
annotation.title=@"位置";
annotation.subtitle=@"我获取的位置";
annotation.coordinate=locationCoordinate;
[_mapView addAnnotation:annotation];

效果如下:

其中userTrackingMode:跟踪类型,是一个枚举:

MKUserTrackingModeNone :不进行用户位置跟踪;
MKUserTrackingModeFollow :跟踪用户位置;
MKUserTrackingModeFollowWithHeading :跟踪用户位置并且跟踪用户前进方向;

如果你设置了用户位置跟踪,你也可以省略上面第一步中的显示用户所在区域,现在会自动显示区域范围并指定当前用户位置为地图中心点

我们还设置了mapType:地图类型,同样是一个枚举:

MKMapTypeStandard :标准地图,一般情况下使用此地图即可满足;
MKMapTypeSatellite :卫星地图;
MKMapTypeHybrid :混合地图,加载最慢比较消耗资源;

你有没有注意到上面的annotation即大头针,它是一个我们自己定义的类。只要一个NSObject子中类实现MKAnnotation协议就可以作为一个大头针,通常会重写协议中coordinate(标记位置)、title(标题)、subtitle(子标题)三个属性,然后在程序中创建大头针对象并调用addAnnotation:方法添加大头针即可。如下:

@interface MyAnnotation : NSObject<MKAnnotation>

@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;

2.个性化大头针

只要实现以下方法并在这个方法中定义一个大头针视图MKAnnotationView对象并设置相关属性就可以改变默认大头针的样式。

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;

方法可以返回一个大头针视图,MKAnnotationView常用属性:

image:大头针图片
canShowCallout:点击大头针是否显示标题、子标题内容等,需要设置为true。
calloutOffset:点击大头针时弹出详情信息视图的偏移量
selected:是否被选中状态
leftCalloutAccessoryView:弹出详情左侧视图
rightCalloutAccessoryView:弹出详情右侧视图

我们还可以使用它的子类:MKPinAnnotationView,该子类包含了两个属性,MKPinAnnotationColor pinColor和BOOL animatesDrop,分别可以设置大头针颜色和是否显示动画效果(即大头针从天而降的效果)
需要注意:

a.这个代理方法的调用时机:每当有大头针显示到系统可视界面中时就会调用此方法返回一个大头针视图放到界面中,同时当前系统位置标注(也就是地图中蓝色的位置点)也是一个大头针,也会调用此方法,因此处理大头针视图时需要区别对待。

b.我们使用dequeueReusableAnnotationViewWithIdentifier来缓存利用大头针view,类似于UITableView的代理方法;

c.自定义大头针默认情况下不允许交互,如果交互需要设置canShowCallout=true

d.如果代理方法返回nil则会使用默认大头针视图,需要根据情况设置。

代码如下:

-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
    if([annotation isKindOfClass:[MyAnnotation class]]){
    static NSString *annotationIdentifier = @"AnnotationIdentifier";
    
    MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
    
    if (!pinView){
        pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
        [pinView setPinColor:MKPinAnnotationColorGreen];
        pinView.animatesDrop = YES;
        pinView.canShowCallout = YES;
        UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"a"]];
        pinView.leftCalloutAccessoryView = iconView;
    }
    else {
        pinView.annotation = annotation;
    }
    
    return pinView;
    }
    return nil;
}

效果如下:

你会不会觉得还不够个性化,下面我们继续修改它的样式。

注意上面的代码中

if([annotation isKindOfClass:[MyAnnotation class]])

可得知,我们要加载其它样式的MKAnnotationView,我们同样需要另外一个MKAnnotation,所以:

然后添加如下代码记得不要忘了<MKAnnotation>:

@interface CustomAnnotation : NSObject<MKAnnotation>

@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;

@property (nonatomic,strong) UIImage *image;
@property (nonatomic,strong) UIImage *icon;
@property (nonatomic,copy) NSString *detail;
@property (nonatomic,strong) UIImage *detailView;

和MKPinAnnotationView思路一样,我们需要新建一个MKAnnotationView的子类,如下:

然后我们把那堆讨厌的 if (!pinView)~~ initWithAnnotation:annotation reuseIdentifier~~写在自定义子类的类方法中,如下:

+(instancetype)layoutMyPinViewWithMapView:(MKMapView *)mapView{
    static NSString *annotationIdentifier = @"AnnotationIdentifier2";
    MyAnnotationView *pinView = (MyAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
    
    if (!pinView){
        pinView = [[self alloc]init];
    }
    return pinView;
}

初始化:

-(instancetype)init{
    if(self=[super init]){
        [self layoutUI];
    }
    return self;
}

-(void)layoutUI{
    _backgroundView=[[UIView alloc]init];
    _backgroundView.backgroundColor=[UIColor whiteColor];
    _iconView=[[UIImageView alloc]init];
    
    _detailLabel=[[UILabel alloc]init];
    _detailLabel.lineBreakMode=NSLineBreakByWordWrapping;
    _detailLabel.font=[UIFont systemFontOfSize:kDetailFontSize];
    
    _detailView=[[UIImageView alloc]init];
    
    [self addSubview:_backgroundView];
    [self addSubview:_iconView];
    [self addSubview:_detailLabel];
    [self addSubview:_detailView];
}

然后在annotation的set方法中进行布局:

-(void)setAnnotation:(CustomAnnotation *)annotation{
    [super setAnnotation:annotation];
    //根据模型调整布局
    _iconView.image=annotation.icon;
    _iconView.frame=CGRectMake(kSpacing, kSpacing, annotation.icon.size.width, annotation.icon.size.height);
    
    _detailLabel.text=annotation.detail;
    float detailWidth=150.0;
    CGSize detailSize= [annotation.detail boundingRectWithSize:CGSizeMake(detailWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kDetailFontSize]} context:nil].size;
    float detailX=CGRectGetMaxX(_iconView.frame)+kSpacing;
    _detailLabel.frame=CGRectMake(detailX, kSpacing, detailSize.width, detailSize.height);
    _detailView.image=annotation.detailView;
    _detailView.frame=CGRectMake(detailX, CGRectGetMaxY(_detailLabel.frame)+kSpacing, annotation.detailView.size.width, annotation.detailView.size.height);
    
    float backgroundWidth=CGRectGetMaxX(_detailLabel.frame)+kSpacing;
    float backgroundHeight=_iconView.frame.size.height+2*kSpacing;
    _backgroundView.frame=CGRectMake(0, 0, backgroundWidth*2, backgroundHeight);
    self.bounds=CGRectMake(0, 0, backgroundWidth, backgroundHeight+kViewOffset);
}

因为最开始显示两个大头针的时候,我们是不需要我们自定义的MKAnnotationView子类,只需要把大头针的image改了即可,所以我们像上面那样写:

        MyAnnotation *annotation=[[MyAnnotation alloc]init];
        annotation.title=@"位置1";
        annotation.subtitle=@"我获取的位置";
        annotation.image=[UIImage imageNamed:@"locationImage1"];
        annotation.icon=[UIImage imageNamed:@"iconMark"];
        annotation.detail=@"哈哈";
        annotation.detailView=[UIImage imageNamed:@"iconStar"];
        annotation.coordinate=locationCoordinate;
        [wself.mapView addAnnotation:annotation];
        
        //添加我们自定义的大头针
        CLLocationCoordinate2D location2=CLLocationCoordinate2DMake(locationCoordinate.latitude+0.002, locationCoordinate.longitude+0.002);
        MyAnnotation *annotation2 = [[MyAnnotation alloc]init];
        annotation2.title=@"位置2";
        annotation2.subtitle=@"我自定义的位置";
        annotation2.coordinate=location2;
        annotation2.image=[UIImage imageNamed:@"locationImage"];
        annotation2.icon=[UIImage imageNamed:@"iconMark"];
        annotation2.detail=@"呵呵";
        annotation2.detailView=[UIImage imageNamed:@"iconStar"];
        [wself.mapView addAnnotation:annotation2];

然后在下面的代理方法中:

-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
    if([annotation isKindOfClass:[MyAnnotation class]]){
        static NSString *annotationIdentifier = @"AnnotationIdentifier";
        MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
        
        if (!pinView){
            pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
            //[pinView setPinColor:MKPinAnnotationColorGreen];
            //pinView.animatesDrop = YES;
            pinView.calloutOffset=CGPointMake(0, 1);
            UIImageView *iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"a"]];
            pinView.leftCalloutAccessoryView = iconView;
        }
        pinView.annotation = annotation;
        pinView.image=((MyAnnotation *)annotation).image;
        return pinView;
    }else if ([annotation isKindOfClass:[CustomAnnotation class]]){
        //
    }else{
        return nil;
    }
}

上面需要注意的是,不可以调用以下方法,否则大头针的image不显示出来。

[pinView setPinColor:MKPinAnnotationColorGreen];
pinView.animatesDrop = YES;

而且上面这句

else if ([annotation isKindOfClass:[CustomAnnotation class]]){

目前不会调用到,因为我们新建的两个annotation都是MyAnnotation。而且需要注意的是,因为我们没有设置pinView.canShowCallout = YES,所以canShowCallout默认为NO,点击annotation后不会有详情view显示出来。所以我们需要在点击annotation是加载我们自定义的CustomAnnotation

-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
    MyAnnotation *annotation=view.annotation;
    if ([view.annotation isKindOfClass:[MyAnnotation class]]) {
        CustomAnnotation *annotation1=[[CustomAnnotation alloc]init];
        annotation1.icon=annotation.icon;
        annotation1.detail=annotation.detail;
        annotation1.detailView=annotation.detailView;
        annotation1.coordinate=view.annotation.coordinate;
        [mapView addAnnotation:annotation1];
    }
}

然后在viewForAnnotation中加载我们的MyAnnotationView:

else if ([annotation isKindOfClass:[CustomAnnotation class]]){
        MyAnnotationView *myView=[MyAnnotationView layoutMyPinViewWithMapView:_mapView];
        myView.canShowCallout = NO;
        myView.annotation = annotation;
        return myView;
}

还需要在点击除大头针之外区域时移除大头针:

#pragma mark 取消选中时触发
-(void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{
    [self removeCustomAnnotation];
}

#pragma mark 移除所用自定义大头针
-(void)removeCustomAnnotation{
    [_mapView.annotations enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if ([obj isKindOfClass:[CustomAnnotation class]]) {
            [_mapView removeAnnotation:obj];
        }
    }];
}

好了,看效果:

你可以在这里下载到本文的代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容