1、创建 MKMapView 地图
- 在 iOS6 或者 iOS7 中实现这个功能只需要添加地图控件、设置用户跟踪模式、在 mapView:didUpdateUserLocation: 代理方法中设置地图中心区域及显示范围。
- 在 iOS8+ 中用法稍有不同:
- a. 由于在地图中进行用户位置跟踪需要使用定位功能,而定位功能在 iOS8 中设计发生了变化,因此必须按照定位中提到的内容进行配置和请求。
- b. iOS8+ 中不需要进行中心点的指定,默认会将当前位置设置中心点并自动设置显示区域范围。
// 包含头文件 #import <CoreLocation/CoreLocation.h> #import <MapKit/MapKit.h> // 遵守协议 <MKMapViewDelegate, CLLocationManagerDelegate> // 声明地图控件 @property (nonatomic, strong) MKMapView *mapView;
-
1、请求定位
// 实例化定位管理器 CLLocationManager *locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; // 判断系统定位服务是否开启 if (![CLLocationManager locationServicesEnabled]) { NSLog(@"%@", @"提示:系统定位服务不可用,请开启 !"); } else { // 判断应用定位服务授权状态 if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){ // 没有授权 // 8.0 及以上系统需手动请求定位授权 if ([UIDevice currentDevice].systemVersion.doubleValue >= 8.0) { // 前台定位,需在 info.plist 里设置 Privacy - Location When In Use Usage Description 的值 [locationManager requestWhenInUseAuthorization]; // 前后台同时定位,需在 info.plist 里设置 Privacy - Location Always Usage Description 的值 // [self.locationManager requestAlwaysAuthorization]; } // 开始定位追踪(第一次打开软件时) [locationManager startUpdatingLocation]; } else if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways) { // 允许定位授权 // 开始定位追踪 [locationManager startUpdatingLocation]; } else{ // 拒绝定位授权 // 创建警告框(自定义方法) NSLog(@"%@", @"提示:当前应用的定位服务不可用,请检查定位服务授权状态 !"); } }
-
2、创建地图
/* mapType: MKMapTypeStandard = 0, 标准类型 MKMapTypeSatellite, 卫星图 MKMapTypeHybrid 混合类型 userTrackingMode:用户位置追踪用于标记用户当前位置,此时会调用定位服务,必须先设置定位请求 MKUserTrackingModeNone = 0, 不跟踪用户位置 MKUserTrackingModeFollow, 跟踪并在地图上显示用户的当前位置 MKUserTrackingModeFollowWithHeading, 跟踪并在地图上显示用户的当前位置,地图会跟随用户的前进方向进行旋转 */ // 实例化地图控件 self.mapView = [[MKMapView alloc] initWithFrame:self.view.bounds]; self.mapView.delegate = self; // 设置地图类型 self.mapView.mapType = MKMapTypeStandard; // 设置跟踪模式 self.mapView.userTrackingMode = MKUserTrackingModeFollow; [self.view addSubview:self.mapView];
#pragma mark - MKMapViewDelegate 协议方法 // 更新到用户的位置 - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{ // 只要用户位置改变就调用此方法(包括第一次定位到用户位置),userLocation:是对用来显示用户位置的蓝色大头针的封装 // 反地理编码 [[[CLGeocoder alloc] init] reverseGeocodeLocation:userLocation.location completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *placemark = [placemarks firstObject]; // 设置用户位置蓝色大头针的标题 userLocation.title = [NSString stringWithFormat:@"当前位置:%@, %@, %@", placemark.thoroughfare, placemark.locality, placemark.country]; }]; // 设置用户位置蓝色大头针的副标题 userLocation.subtitle = [NSString stringWithFormat:@"经纬度:(%lf, %lf)", userLocation.location.coordinate.longitude, userLocation.location.coordinate.latitude]; // 手动设置显示区域中心点和范围 if ([UIDevice currentDevice].systemVersion.floatValue < 8.0) { // 显示的中心 CLLocationCoordinate2D center = userLocation.location.coordinate; // 设置地图显示的中心点 [self.mapView setCenterCoordinate:center animated:YES]; // 设置地图显示的经纬度跨度 MKCoordinateSpan span = MKCoordinateSpanMake(0.023503, 0.017424); // 设置地图显示的范围 MKCoordinateRegion rengion = MKCoordinateRegionMake(center, span); [self.mapView setRegion:rengion animated:YES]; } } // 地图显示的区域将要改变 - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated { NSLog(@"区域将要改变:经度:%lf, 纬度:%lf, 经度跨度:%lf, 纬度跨度:%lf", mapView.region.center.longitude, mapView.region.center.latitude, mapView.region.span.longitudeDelta, mapView.region.span.latitudeDelta); } // 地图显示的区域改变了 - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { NSLog(@"区域已经改变:经度:%lf, 纬度:%lf, 经度跨度:%lf, 纬度跨度:%lf", mapView.region.center.longitude, mapView.region.center.latitude, mapView.region.span.longitudeDelta, mapView.region.span.latitudeDelta); }
-
效果
2、添加大头针
2.1 添加大头针
-
自定义大头针模型
-
QAnnotation.h
#import <MapKit/MapKit.h> @interface QAnnotation : NSObject <MKAnnotation> @property (nonatomic, copy)NSString *title; @property (nonatomic, copy)NSString *subtitle; @property (nonatomic, copy)NSString *icon; @property (nonatomic, assign)CLLocationCoordinate2D coordinate; /// 初始化大头针模型 + (instancetype)q_annotationWithTitle:(NSString *)title subTitle:(NSString *)subTitle icon:(NSString *)icon coordinate:(CLLocationCoordinate2D)coordinate; @end
-
QAnnotation.m
@implementation QAnnotation /// 初始化大头针模型 + (instancetype)q_annotationWithTitle:(NSString *)title subTitle:(NSString *)subTitle icon:(NSString *)icon coordinate:(CLLocationCoordinate2D)coordinate{ QAnnotation *annotation = [[self alloc] init]; annotation.title = title; annotation.subtitle = subTitle; annotation.icon = icon; annotation.coordinate = coordinate; return annotation; } @end
-
-
添加大头针
-
ViewController.m
#import "QAnnotation.h" // 首先创建 MKMapView 地图 // 设置大头针显示的内容 NSString *title = @"xxx大饭店"; NSString *subtitle = @"全场一律15折,会员20折"; NSString *icon = @"category_1"; // 设置大头针放置的位置 CLLocationCoordinate2D cl2d = CLLocationCoordinate2DMake(40.1020, 116.3265); // 初始化大头针模型 QAnnotation *annotation = [QAnnotation q_annotationWithTitle:title subTitle:subtitle icon:icon coordinate:cl2d]; // 在地图上添加大头针控件 [self.mapView addAnnotation:annotation];
-
效果
-
2.2 设置大头针样式
-
设置大头针样式
// MKMapViewDelegate 协议方法 - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { /* 显示大头针时触发,返回大头针视图,通常自定义大头针可以通过此方法进行。 使用遵守协议 <MKAnnotation> 的模型,写此方法时所有遵守协议 <MKAnnotation> 的大头针模型都会改变,不写时为默认样式的大头针。 */ // 判断大头针模型是否属于 QAnnotation 类 if ([annotation isKindOfClass:[QAnnotation class]]) { // 显示自定义样式的大头针 // 获得大头针控件,利用自定义的( QAnnotationView )大头针控件创建 QAnnotationView *annotationView = [QAnnotationView q_annotationViewWithMapView:mapView]; // 传递模型,更新大头针数据,覆盖掉之前的旧数据 annotationView.annotation = annotation; return annotationView; } else { // 显示系统样式的大头针 // 先从缓存池中取出可以循环利用的大头针控件,利用带针的( MKPinAnnotationView )子类大头针控件创建 MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"qianchia"]; // 缓存池中没有可以利用的大头针控件 if (annotationView == nil) { // 创建大头针控件 annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@"qianchia"]; // 设置大头针头的颜色 annotationView.pinColor = MKPinAnnotationColorGreen; // 大头针从天而降 annotationView.animatesDrop = YES; // 显示大头针标题和子标题 annotationView.canShowCallout = YES; // 设置子菜单的偏移量 annotationView.calloutOffset = CGPointMake(0, -10); // 自定义子菜单的左右视图 annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeContactAdd]; annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeInfoDark]; } // 传递模型,更新大头针数据,覆盖掉之前的旧数据 annotationView.annotation = annotation; return annotationView; } }
-
效果
2.3 不同大头针样式的创建
-
1、带针的大头针
// 先从缓存池中取出可以循环利用的大头针控件,利用带针的( MKPinAnnotationView )子类大头针控件创建 MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"qianchia"]; // 缓存池中没有可以利用的大头针控件 if (annotationView == nil) { // 创建大头针控件 annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@"qianchia"]; // 设置大头针头的颜色 annotationView.pinColor = MKPinAnnotationColorGreen; // 大头针从天而降 annotationView.animatesDrop = YES; // 显示大头针标题和子标题 annotationView.canShowCallout = YES; // 设置子菜单的偏移量 annotationView.calloutOffset = CGPointMake(0, -10); // 自定义子菜单的左右视图 annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeContactAdd]; annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeInfoDark]; } // 传递模型,更新大头针数据,覆盖掉之前的旧数据 annotationView.annotation = annotation; return annotationView;
-
效果
-
-
2、不带针的大头针
// 先从缓存池中取出可以循环利用的大头针控件 利用不带针的( MKAnnotationView )父类大头针控件创建 MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"qianchia"]; // 缓存池中没有可以利用的大头针控件 if (annotationView == nil) { // 创建大头针控件 annotationView = [[MKAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@"qianchia"]; // 显示大头针标题和子标题 annotationView.canShowCallout = YES; // 设置子菜单的偏移量 annotationView.calloutOffset = CGPointMake(0, -10); // 自定义子菜单的左右视图 annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeContactAdd]; annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeInfoDark]; } // 传递模型,更新大头针数据,覆盖掉之前的旧数据 annotationView.annotation = annotation; // 设置大头针的图片,所有大头针图片相同 annotationView.image = [UIImage imageNamed:@"category_4"]; return annotationView;
-
效果
-
-
3、自定义类型的大头针
-
QAnnotationView.h
@interface QAnnotationView : MKAnnotationView /// 创建大头针控件 + (instancetype)q_annotationViewWithMapView:(MKMapView *)mapView; @end
-
QAnnotationView.m
#import "QAnnotation.h" #import "UIView+Frame.h" @interface QAnnotationView () /// 自定义大头针子菜单图片视图 @property (nonatomic, strong) UIImageView *iconView; @end @implementation QAnnotationView #pragma mark - 创建大头针控件 /// 创建大头针控件 + (instancetype)q_annotationViewWithMapView:(MKMapView *)mapView { QAnnotationView *annotationView = (QAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"qianchia"]; if (annotationView == nil) { annotationView = [[self alloc] initWithAnnotation:nil reuseIdentifier:@"qianchia"]; } return annotationView; } /// 重写初始化大头针控件方法 - (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]) { // 显示自定义大头针的标题和子标题 self.canShowCallout = YES; // 设置自定义大头针的子菜单左边显示一个图片 UIImageView *imageView = [[UIImageView alloc] init]; imageView.bounds = CGRectMake(0, 0, 40, 50); self.iconView = imageView; // 设置自定义大头针的子菜单左边视图 self.leftCalloutAccessoryView = self.iconView; } return self; } /// 重写大头针模型的 setter 方法 - (void)setAnnotation:(QAnnotation *)annotation{ [super setAnnotation:annotation]; // 设置自定义大头针图片 self.image = [UIImage imageNamed:annotation.icon]; // 设置自定义大头针的子菜单图片 self.iconView.image = [UIImage imageNamed:annotation.icon]; } @end
-
ViewController.m
// 判断大头针模型是否属于 QAnnotation 类 if ([annotation isKindOfClass:[QAnnotation class]]) { // 获得大头针控件,利用自定义的( QAnnotationView )大头针控件创建 QAnnotationView *annotationView = [QAnnotationView q_annotationViewWithMapView:mapView]; // 传递模型,更新大头针数据,覆盖掉之前的旧数据 annotationView.annotation = annotation; return annotationView; }
-
效果
-
3、地图画线
-
设置起点和终点
// 获取起点和终点 NSString *sourceAddress = [alertView textFieldAtIndex:0].text; NSString *destinationAddress = [alertView textFieldAtIndex:1].text; // 地理编码 起点 [[[CLGeocoder alloc] init] geocodeAddressString:sourceAddress completionHandler:^(NSArray *placemarks, NSError *error) { if (placemarks == nil || error) { return; } else { CLPlacemark *sourcePlacemark = [placemarks firstObject]; // 移除以前的大头针 if (self.sourceAnnotation) { [self.mapView removeAnnotation:self.sourceAnnotation]; } // 添加新的大头针 self.sourceAnnotation = [QAnnotation q_annotationWithTitle:sourceAddress subTitle:sourcePlacemark.name icon:nil coordinate:sourcePlacemark.location.coordinate]; [self.mapView addAnnotation:self.sourceAnnotation]; // 地理编码 终点 [[[CLGeocoder alloc] init] geocodeAddressString:destinationAddress completionHandler:^(NSArray *placemarks, NSError *error) { if (placemarks == nil || error) { return; } else { CLPlacemark *destinationPlacemark = [placemarks firstObject]; // 移除以前的大头针 if (self.destinationAnnotation) { [self.mapView removeAnnotation:self.destinationAnnotation]; } // 添加新的大头针 self.destinationAnnotation = [QAnnotation q_annotationWithTitle:destinationAddress subTitle:destinationPlacemark.name icon:nil coordinate:destinationPlacemark.location.coordinate]; [self.mapView addAnnotation:self.destinationAnnotation]; // 开始画线 [self drawLineWithSourceCLPlacemark:sourcePlacemark destinationCLPlacemark:destinationPlacemark]; } }]; } }];
-
开始画线
// 自定义方法 - (void)drawLineWithSourceCLPlacemark:(CLPlacemark *)sourceCLPm destinationCLPlacemark:(CLPlacemark *)desinationCLPm{ // 初始化方向请求 MKDirectionsRequest *dRequest = [[MKDirectionsRequest alloc] init]; // 设置起点( CLPlacemark --> MKPlacemark ) MKPlacemark *sourceMKPm = [[MKPlacemark alloc] initWithPlacemark:sourceCLPm]; dRequest.source = [[MKMapItem alloc] initWithPlacemark:sourceMKPm]; // 设置终点( CLPlacemark --> MKPlacemark ) MKPlacemark *destinationMKPm = [[MKPlacemark alloc] initWithPlacemark:desinationCLPm]; dRequest.destination = [[MKMapItem alloc] initWithPlacemark:destinationMKPm]; // 根据请求创建方向 MKDirections *directions = [[MKDirections alloc] initWithRequest:dRequest]; // 执行请求 [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) { if (error) { return; } else { // 移除所有已画的线,移除旧的线 [self.mapView removeOverlays:self.mapView.overlays]; for (MKRoute *route in response.routes) { // 添加路线,传递路线的遮盖模型数据 [self.mapView addOverlay:route.polyline]; } } }]; }
-
设置画线属性
// MKMapViewDelegate 协议方法 - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{ MKPolylineRenderer *rederer = [[MKPolylineRenderer alloc] initWithOverlay:overlay]; rederer.lineWidth = 5; // 设置线宽 rederer.strokeColor = [UIColor blueColor]; // 设置线的颜色 return rederer; }
-
效果
4、地图导航
4.1 创建导航
-
设置起点和终点
// 获取起点和终点 NSString *sourceAddress = [alertView textFieldAtIndex:0].text; NSString *destinationAddress = [alertView textFieldAtIndex:1].text; // 地理编码 起点 [[[CLGeocoder alloc] init] geocodeAddressString:sourceAddress completionHandler:^(NSArray *placemarks, NSError *error) { if (placemarks == nil || error) { return; } else { CLPlacemark *sourcePlacemark = [placemarks firstObject]; // 地理编码 终点 [[[CLGeocoder alloc] init] geocodeAddressString:destinationAddress completionHandler:^(NSArray *placemarks, NSError *error) { if (placemarks == nil || error) { return; } else { CLPlacemark *destinationPlacemark = [placemarks firstObject]; // 开始导航 [self startNavigationWithSourceCLPlacemark:sourcePlacemark destinationCLPlacemark:destinationPlacemark]; } }]; } }];
-
开始导航
设置导航参数: MKLaunchOptionsDirectionsModeKey // 导航模式 Key to a directions mode MKLaunchOptionsMapTypeKey // 地图类型 Key to an NSNumber corresponding to a MKMapType MKLaunchOptionsShowsTrafficKey // 交通路况 Key to a boolean NSNumber // Directions modes MKLaunchOptionsDirectionsModeDriving // 驾驶模式 MKLaunchOptionsDirectionsModeWalking // 步行模式 // If center and span are present, having a camera as well is undefined MKLaunchOptionsMapCenterKey // 地图中心 Key to an NSValue-encoded CLLocationCoordinate2D MKLaunchOptionsMapSpanKey // 地图跨度 Key to an NSValue-encoded MKCoordinateSpan MKLaunchOptionsCameraKey // 地图相机 Key to MKMapCamera object
// 自定义方法 - (void)startNavigationWithSourceCLPlacemark:(CLPlacemark *)sourceCLPm destinationCLPlacemark:(CLPlacemark *)desinationCLPm{ if (sourceCLPm == nil || desinationCLPm == nil) { return; } else { // 设置起点( CLPlacemark --> MKPlacemark ) MKPlacemark *sourceMKPm = [[MKPlacemark alloc] initWithPlacemark:sourceCLPm]; MKMapItem *sourceItem = [[MKMapItem alloc] initWithPlacemark:sourceMKPm]; // 设置终点( CLPlacemark --> MKPlacemark ) MKPlacemark *destinationMKPm = [[MKPlacemark alloc] initWithPlacemark:desinationCLPm]; MKMapItem *destinationItem = [[MKMapItem alloc] initWithPlacemark:destinationMKPm]; NSArray *items = @[sourceItem, destinationItem]; // 设置导航参数(导航模式:驾驶导航,是否显示路况:是,地图类型:标准) NSDictionary *options = @{MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving, MKLaunchOptionsShowsTrafficKey: @YES, MKLaunchOptionsMapTypeKey: @(MKMapTypeStandard)}; // 打开苹果官方的导航应用(打开苹果自带地图 App 开始导航) [MKMapItem openMapsWithItems:items launchOptions:options]; } }
-
效果
4.2 快速创建导航
-
从当前位置到指定位置导航
// 根据“北京市”进行地理编码 [_geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark = [placemarks firstObject]; // 获取第一个地标 MKPlacemark *mkplacemark = [[MKPlacemark alloc] initWithPlacemark:clPlacemark]; // 定位地标转化为地图的地标 NSDictionary *options = @{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard)}; MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:mkplacemark]; // 调用苹果地图开始导航,从当前位置到指定位置 [mapItem openInMapsWithLaunchOptions:options]; }];
-
效果
-
-
从位置 1 到位置 2 导航
// 根据“北京市”进行地理编码 [_geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark1 = [placemarks firstObject]; MKPlacemark *mkPlacemark1 = [[MKPlacemark alloc] initWithPlacemark:clPlacemark1]; // 注意地理编码一次只能定位到一个位置,不能同时定位,所在放到第一个位置定位完成回调函数中再次定位 [_geocoder geocodeAddressString:@"郑州市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark2 = [placemarks firstObject]; MKPlacemark *mkPlacemark2 = [[MKPlacemark alloc] initWithPlacemark:clPlacemark2]; NSDictionary *options = @{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard)}; MKMapItem *mapItem1 = [[MKMapItem alloc] initWithPlacemark:mkPlacemark1]; MKMapItem *mapItem2 = [[MKMapItem alloc] initWithPlacemark:mkPlacemark2]; // 调用苹果地图开始导航,从 Item1 到 Item2 [MKMapItem openMapsWithItems:@[mapItem1, mapItem2] launchOptions:options]; }]; }];
-
效果
-