ios开发系统地图知识
现在很多社交、电商、团购应用都引入了地图和定位功能,地图功能不再是地图应用和导航应用所特有的。目前地图和定位功能已经大量引入到应用开发中。今天就和大家一起看一下iOS如何进行地图开发。
一、Core Location定位使用
在iOS中通过Core Location框架进行定位操作。Core Location自身可以单独使用,和地图开发框架MapKit完全是独立的,但是往往地图开发要配合定位框架使用。在Core Location中主要包含了定位、地理编码(包括反编码)功能。
定位是一个很常用的功能,如一些地图软件打开之后如果用户允许软件定位的话,那么打开软件后就会自动锁定到当前位置,如果用户手机移动那么当前位置也会跟随着变化。要实现这个功能需要使用Core Loaction中CLLocationManager类,首先看一下这个类的一些主要方法和属性:
类方法:
//是否启用定位服务,通常如果用户没有启用定位服务可以提示用户打开定位服务
+ (BOOL)locationServicesEnabled;
/*定位服务授权状态,返回枚举类型:
kCLAuthorizationStatusNotDetermined: 用户尚未做出决定是否启用定位服务
kCLAuthorizationStatusRestricted: 没有获得用户授权使用定位服务,可能用户没有自己禁止访问授权
kCLAuthorizationStatusDenied :用户已经明确禁止应用使用定位服务或者当前系统定位服务处于关闭状态
kCLAuthorizationStatusAuthorizedAlways: 应用获得授权可以一直使用定位服务,即使应用不在使用状态
kCLAuthorizationStatusAuthorizedWhenInUse: 使用此应用过程中允许访问定位服务**/
+ (CLAuthorizationStatus)authorizationStatus;
对象方法:
/*开始定位追踪,开始定位后将按照用户设置的更新频率执行-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;方法反馈定位信息**/
-(void)startUpdatingLocation;
stopUpdatingLocation//停止定位追踪
startUpdatingHeading//开始导航方向追踪
stopUpdatingHeading//停止导航方向追踪
/*开始对某个区域进行定位追踪,开始对某个区域进行定位后。如果用户进入或者走出某个区域会调用- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region和- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region代理方法反馈相关信息**/
startMonitoringForRegion
stopMonitoringForRegion://停止对某个区域进行定位追踪
requestWhenInUseAuthorization//请求获得应用使用时的定位服务授权,注意使用此方法前在要在info.plist中配置NSLocationWhenInUseUsageDescription
requestAlwaysAuthorization//请求获得应用一直使用定位服务授权,注意使用此方法前要在info.plist中配置NSLocationAlwaysUsageDescription
代理方法:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;//位置发生改变后执行(第一次定位到某个位置之后也会执行)
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading;//导航方向发生变化后执行
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region//进入某个区域之后执行
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region//走出某个区域之后执行
属性:
/*定位精度,枚举类型:
kCLLocationAccuracyBest:最精确定位
CLLocationAccuracy kCLLocationAccuracyNearestTenMeters:十米误差范围
kCLLocationAccuracyHundredMeters:百米误差范围
kCLLocationAccuracyKilometer:千米误差范围
kCLLocationAccuracyThreeKilometers:三千米误差范围**/
desiredAccuracy//定位精度
distanceFilter//位置信息更新最小距离,只有移动大于这个距离才更新位置信息,默认为kCLDistanceFilterNone:不进行距离限制
* CLLocation 位置对象属性
// 经纬度
@property(readonly, nonatomic) CLLocationCoordinate2D coordinate;
// 海拔,高度
@property(readonly, nonatomic) CLLocationDistance altitude;
// 水平精确度 用于描述经纬度测量值和真实值之间的误差范围
@property(readonly, nonatomic) CLLocationAccuracy horizontalAccuracy;
// 垂直精确度 用于描述高度测量值和真实值之间的误差范围
@property(readonly, nonatomic) CLLocationAccuracy verticalAccuracy;
// 航向
@property(readonly, nonatomic) CLLocationDirection course __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_2_2) __TVOS_PROHIBITED :;
// 瞬时速度
@property(readonly, nonatomic) CLLocationSpeed speed __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_2_2) __TVOS_PROHIBITED __WATCHOS_PROHIBITED;
// 时间戳
@property(readonly, nonatomic, copy) NSDate *timestamp
// 楼层
@property(readonly, nonatomic, copy, nullable) CLFloor *floor __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_8_0);
// 描述
@property (nonatomic, readonly, copy) NSString *description;
提醒:开启临时后台 设置后台运行模式 设置info.plist(Capability)
//iOS9新变化 还需要代码允许后台模式
self.manager.allowsBackgroundLocationUpdates = YES;
1.定位频率和定位精度并不应当越精确越好,需要视实际情况而定,因为越精确越耗性能,也就越费电。
2.定位成功后会根据设置情况频繁调用-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations方法,这个方法返回一组地理位置对象数组,每个元素一个CLLocation代表地理位置信息(包含经度、纬度、海报、行走速度等信息),之所以返回数组是因为有些时候一个位置点可能包含多个位置。
3.使用完定位服务后如果不需要实时监控应该立即关闭定位服务以节省资源。
4.除了提供定位功能,CLLocationManager还可以调用startMonitoringForRegion:方法对指定区域进行监控。
* CLPlacemark 地标对象
// 位置对象,包含地理信息
@property (nonatomic, readonly, copy, nullable) CLLocation *location;
// 区域,范围
@property (nonatomic, readonly, copy, nullable) CLRegion *region;
// 时区
@property (nonatomic, readonly, copy, nullable) NSTimeZone *timeZone NS_AVAILABLE(10_11,9_0);
// 地址字典 以字典的形式包含地址(人文信息)
@property (nonatomic, readonly, copy, nullable) NSDictionary *addressDictionary;
// address dictionary properties
@property (nonatomic, readonly, copy, nullable) NSString *name; // 名称
@property (nonatomic, readonly, copy, nullable) NSString *thoroughfare; // 街道名
@property (nonatomic, readonly, copy, nullable) NSString *subThoroughfare; // 门牌号
@property (nonatomic, readonly, copy, nullable) NSString *locality; // 城市
@property (nonatomic, readonly, copy, nullable) NSString *subLocality; // 区
@property (nonatomic, readonly, copy, nullable) NSString *administrativeArea; // 州,省
@property (nonatomic, readonly, copy, nullable) NSString *subAdministrativeArea; // 郡,县
@property (nonatomic, readonly, copy, nullable) NSString *postalCode; // 邮政编码
@property (nonatomic, readonly, copy, nullable) NSString *ISOcountryCode; // ISO国家代号
@property (nonatomic, readonly, copy, nullable) NSString *country; // 国家
@property (nonatomic, readonly, copy, nullable) NSString *inlandWater; // 内陆河
@property (nonatomic, readonly, copy, nullable) NSString *ocean; // 太平洋
@property (nonatomic, readonly, copy, nullable) NSArray<NSString *> *areasOfInterest; // 感兴趣的点
应用:
一次定位:获取地址后调用停止的方法,也就是在locationManager:(CLLocationManager *)manager didUpdateLocations:方法里调用[self.manager stopUpdatingLocation];
持续定位:不调用[self.manager stopUpdatingLocation]方法;
1.计算两个位置的距离
//获取位置信息对应的位置对象
CLLocation *location1 = [[CLLocation alloc] initWithLatitude:39.9 longitude:116.4];
CLLocation *location2 = [[CLLocation alloc] initWithLatitude:23.0 longitude:113.1];
//计算距离
CGFloat distance = [location1 distanceFromLocation:location2];//单位是米
2.地理编码(人文转地理经纬度)
//1.创建地理编码者
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
//进行地理编码
[geocoder geocodeAddressString:@"北京" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
//NSArray<CLPlacemark *> * _Nullable placemarks 数组<地标对象>
//地标对象 包含地理信息&人文信息
//非空处理
if (placemarks.count == 0 || error) {
NSLog(@"查询失败: %@", error);
return ;
}
CLPlacemark *pm = placemarks.lastObject;
//获取数据
pm.location.coordinate.latitude//纬度
pm.location.coordinate.longitude//经度
pm.name//名称
}];
3.反地理编码(地理经纬度转人文(名称))
//创建地理编码者
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
//获取位置信息对应的位置对象
CLLocation *location = [[CLLocation alloc] initWithLatitude:[self.latitudeTF.text floatValue] longitude:[self.longitudeTF.text floatValue]];
//反地理编码
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
//非空处理
if (placemarks.count == 0 || error) {
NSLog(@"查询失败");
return ;
}
CLPlacemark *pm = placemarks.lastObject;
//获取数据
pm.locality;
}];
iOS 8 之后提供了更加人性化的定位服务选项。App 的定位服务不再仅仅是关闭或打开,现在,定位服务的启用提供了三个选项,「永不」「使用应用程序期间」和「始终」;同时,考虑到能耗问题,如果一款 App 要求始终能在后台开启定位服务,iOS 8 不仅会在首次打开 App 时主动向你询问,还会在日常使用中弹窗提醒你该 App 一直在后台使用定位服务,并询问你是否继续允许。在iOS7及以前的版本,如果在应用程序中使用定位服务只要在程序中调用startUpdatingLocation方法应用就会询问用户是否允许此应用是否允许使用定位服务,同时在提示过程中可以通过在info.plist中配置通过配置Privacy - Location Usage Description告诉用户使用的目的,同时这个配置是可选的。
但是在iOS8中配置配置项发生了变化,可以通过配置NSLocationAlwaysUsageDescription或者NSLocationWhenInUseUsageDescription来告诉用户使用定位服务的目的,并且注意这个配置是必须的,如果不进行配置则默认情况下应用无法使用定位服务,打开应用不会给出打开定位服务的提示,除非安装后自己设置此应用的定位服务。同时,在应用程序中需要根据配置对requestAlwaysAuthorization或locationServicesEnabled方法进行请求。
二、MapKit地图使用
MapKit中地图展示控件MKMapView的的一些常用属性和方法
属性:
userTrackingMode //跟踪类型,是一个枚举:MKUserTrackingModeNone :不进行用户位置跟踪;MKUserTrackingModeFollow :跟踪用户位置;MKUserTrackingModeFollowWithHeading :跟踪用户位置并且跟踪用户前进方向;
mapType//地图类型,是一个枚举:MKMapTypeStandard :标准地图,一般情况下使用此地图即可满足; MKMapTypeSatellite :卫星地图; MKMapTypeHybrid :混合地图,加载最慢比较消耗资源;
userLocation//用户位置,只读属性
annotations//当前地图中的所有大头针,只读属性
对象方法:
- (void)addAnnotation:(id <MKAnnotation>)annotation;//添加大头针,对应的有添加大头针数组
- (void)removeAnnotation:(id <MKAnnotation>)annotation;//删除大头针,对应的有删除大头针数组
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated;//设置地图显示区域,用于控制当前屏幕显示地图范围
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated;//设置地图中心点位置
- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view;//将地理坐标(经纬度)转化为数学坐标(UIKit坐标)
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view;//将数学坐标转换为地理坐标
- (MKAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier;//从缓存池中取出大头针,类似于UITableView中取出UITableViewCell,为了进行性能优化而设计
- (void)selectAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated;//选中指定的大头针
- (void)deselectAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated;//取消选中指定的大头针
代理方法:
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation ;//用户位置发生改变时触发(第一次定位到用户位置也会触发该方法
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation ;//显示区域发生改变后触发
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView;//地图加载完成后触发
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;//显示大头针时触发,返回大头针视图,通常自定义大头针可以通过此方法进行
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view //点击选中某个大头针时触发
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view//取消选中大头针时触发
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay//渲染地图覆盖物时触发
*mapView属性:
//设置罗盘,指南针
self.mapView.showsCompass = YES;
//设置标尺 mi 英里 = 1.5km
self.mapView.showsScale = YES;
//设置显示交通情况
self.mapView.showsTraffic = YES;
//显示定位大头针
self.mapView.showsUserLocation = YES;
//显示感兴趣的点
self.mapView.showsPointsOfInterest = YES;
// self.mapView.camera = [MKMapCamera cameraLookingAtCenterCoordinate:self.mapView.userLocation.location.coordinate fromDistance:100 pitch:45 heading:0];
//在3D(沙盘/航拍)模式下显示建筑的3D模型 高德地图不支持
self.mapView.showsBuildings = YES;
使用:
MKCoordinateRegionMake的两个参数:MKCoordinateRegion&MKCoordinateSpan
typedef struct {
CLLocationCoordinate2D center; 中心点 确定地图的位置
MKCoordinateSpan span; 经纬度跨度 确定地图的大小
} MKCoordinateRegion;//显示范围大小
typedef struct {
CLLocationDegrees latitudeDelta; 纬度跨度 1°=111km
CLLocationDegrees longitudeDelta; 经度跨度
} MKCoordinateSpan;//区域跨度
//设置地图显示范围(如果不进行区域设置会自动显示区域范围并指定当前用户位置为地图中心点)
MKCoordinateSpan span=MKCoordinateSpanMake(0.01, 0.01);
MKCoordinateRegion region=MKCoordinateRegionMake(userLocation.location.coordinate, span);
[_mapView setRegion:region animated:true];
1.放大
//设置地图范围
//设置中心点为当前地图范围的中心点
CLLocationCoordinate2D center = self.mapView.region.center;
//设置跨度为当前地图范围的跨度 * 比例系数
MKCoordinateSpan span = MKCoordinateSpanMake(self.mapView.region.span.latitudeDelta * 0.5, self.mapView.region.span.longitudeDelta * 0.5);
//设置范围
[self.mapView setRegion:MKCoordinateRegionMake(center, span) animated:YES];
2.缩小
//设置地图范围
//设置中心点为当前地图范围的中心点
CLLocationCoordinate2D center = self.mapView.region.center;
//设置跨度为当前地图范围的跨度 * 比例系数
MKCoordinateSpan span = MKCoordinateSpanMake(self.mapView.region.span.latitudeDelta * 2, self.mapView.region.span.longitudeDelta * 2);
//设置范围
[self.mapView setRegion:MKCoordinateRegionMake(center, span) animated:YES];
3.返回定位点
//设置"返回"
//方式1 : 以动画方式设置用户跟踪模式
[self.mapView setUserTrackingMode:MKUserTrackingModeFollow animated:YES];
//方式2: 设置地图范围 到定位点
//中心点为定位点经纬度
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(self.mapView.userLocation.location.coordinate.latitude, self.mapView.userLocation.location.coordinate.longitude);
//跨度为地图当前的跨度
//设置地图范围
[self.mapView setRegion:MKCoordinateRegionMake(center, self.mapView.region.span) animated:YES];
4.切换地图类型
/**
* 地图类型
MKMapTypeStandard = 0, 标准地图
MKMapTypeSatellite, 卫星地图
MKMapTypeHybrid, 混合地图
MKMapTypeSatelliteFlyover NS_ENUM_AVAILABLE(10_11, 9_0), 卫星鸟瞰 目前中国暂时不支持
MKMapTypeHybridFlyover NS_ENUM_AVAILABLE(10_11, 9_0), 混合鸟瞰 目前中国暂时不支持
*/
self.mapView.mapType = MKMapTypeStandard;//标准地图
iOS8中不需要进行中心点的指定,默认会将当前位置设置中心点并自动设置显示区域范围。
了解以上,要进行用户位置跟踪其实就相当简单了,值得一提的是-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation这个代理方法。这个方法只有在定位(利用前面章节中的定位内容)到当前位置之后就会调用,以后每当用户位置发生改变就会触发,调用频率相当频繁。
*大头针KCAnnotation
在iOS开发中经常会标记某个位置,需要使用地图标注,也就是大家俗称的“大头针”。只要一个NSObject类实现MKAnnotation协议就可以作为一个大头针,通常会重写协议中coordinate(标记位置)、title(标题)、subtitle(子标题)三个属性,然后在程序中创建大头针对象并调用addAnnotation:方法添加大头针即可(之所以iOS没有定义一个基类实现这个协议供开发者使用,多数原因应该是MKAnnotation是一个模型对象,对于多数应用模型会稍有不同,例如后面的内容中会给大头针模型对象添加其他属性)。
1.添加大头针
CLLocationCoordinate2D location1=CLLocationCoordinate2DMake(39.95, 116.35); KCAnnotation *annotation1=[[KCAnnotation alloc]init]; annotation1.title=@"北京"; annotation1.subtitle=@"天安门"; annotation1.coordinate=location1; [_mapView addAnnotation:annotation1];
设置大头针视图
在一些应用中系统默认的大头针样式可能无法满足实际的需求,此时就需要修改大头针视图默认样式。根据前面MapKit的代理方法不难发现- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;方法可以返回一个大头针视图,只要实现这个方法并在这个方法中定义一个大头针视图MKAnnotationView对象并设置相关属性就可以改变默认大头针的样式。
在MapKit框架中除了MKAnnotationView之外还有一个MKPinAnnotationView,它是MKAnnotationView的子类,相比MKAnnotationView多了两个属性pinColor和animationDrop,分别用于设置大头针视图颜色和添加大头针动画。
MKAnnotationView常用属性:
annotation//大头针模型信息,包括标题、子标题、地理位置。
image//大头针图片
canShowCallout//点击大头针是否显示标题、子标题内容等,注意如果在- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;方法中重新定义大头针默认情况是无法交互的需要设置为true。
calloutOffset//点击大头针时弹出详情信息视图的偏移量
selected//是否被选中状态
leftCalloutAccessoryView//弹出详情左侧视图
rightCalloutAccessoryView//弹出详情右侧视图
代理方法:
#pragma mark - 地图控件代理方法 #pragma mark 显示大头针时调用,注意方法中的annotation参数是即将显示的大头针对象 -(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{ //由于当前位置的标注也是一个大头针,所以此时需要判断,此代理方法返回nil使用默认大头针视图 if ([annotation isKindOfClass:[KCAnnotation class]]) { static NSString *key1=@"AnnotationKey1"; MKAnnotationView *annotationView=[_mapView dequeueReusableAnnotationViewWithIdentifier:key1]; //如果缓存池中不存在则新建 if (!annotationView) { annotationView=[[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:key1]; annotationView.canShowCallout=true;//允许交互点击 annotationView.calloutOffset=CGPointMake(0, 1);//定义详情视图偏移量 annotationView.leftCalloutAccessoryView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_classify_cafe.png"]];//定义详情左侧视图 } //修改大头针视图 //重新设置此类大头针视图的大头针模型(因为有可能是从缓存池中取出来的,位置是放到缓存池时的位置) annotationView.annotation=annotation; annotationView.image=((KCAnnotation *)annotation).image;//设置大头针视图的图片 return annotationView; }else { return nil; } }
需要注意:
a.这个代理方法的调用时机:每当有大头针显示到系统可视界面中时就会调用此方法返回一个大头针视图放到界面中,同时当前系统位置标注(也就是地图中蓝色的位置点)也是一个大头针,也会调用此方法,因此处理大头针视图时需要区别对待。
b.类似于UITableView的代理方法,此方法调用频繁,开发过程中需要重复利用MapKit的缓存池将大头针视图缓存起来重复利用。
c.自定义大头针默认情况下不允许交互,如果交互需要设置canShowCallout=true
d.如果代理方法返回nil则会使用默认大头针视图,需要根据情况设置。
扩展--自定义大头针弹详情视图
MKAnnotationView足够强大(何况还有MKPinAnnotationView),很多信息都可以进行设置,但是唯独不能修改大头针描述详情视图(仅仅支持详情中左右视图内容)。要实现这个需求目前开发中普遍采用的思路就是:
a.点击一个大头针A时重新在A的坐标处添加另一个大头针B(注意此时将A对应的大头针视图canShowCallout设置为false)作为大头针详情模型,然后在- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;代理方法中判断大头针类型,如果是B则重写MKAnnotationView(可以自定义一个类C继承于MKAnnotationView),返回自定义大头针视图C。
b.定义大头针视图C继承于MKAnnotationView(或者MKPinAnnotationView),在自定义大头针视图中添加自己的控件,完成自定义布局。
大头针模型:KCAnnotation.h
#import <Foundation/Foundation.h> #import <MapKit/MapKit.h> @interface KCAnnotation : NSObject<MKAnnotation> @property (nonatomic) CLLocationCoordinate2D coordinate; @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *subtitle; #pragma mark 自定义一个图片属性在创建大头针视图时使用 @property (nonatomic,strong) UIImage *image; #pragma mark 大头针详情左侧图标 @property (nonatomic,strong) UIImage *icon; #pragma mark 大头针详情描述 @property (nonatomic,copy) NSString *detail; #pragma mark 大头针右下方星级评价 @property (nonatomic,strong) UIImage *rate; @end
弹出详情大头针模型:KCCalloutAnnotation.h
#import <UIKit/UIKit.h> #import <CoreLocation/CoreLocation.h> #import <MapKit/MapKit.h> @interface KCCalloutAnnotation : NSObject<MKAnnotation> @property (nonatomic) CLLocationCoordinate2D coordinate; @property (nonatomic, copy,readonly) NSString *title; @property (nonatomic, copy,readonly) NSString *subtitle; #pragma mark 左侧图标 @property (nonatomic,strong) UIImage *icon; #pragma mark 详情描述 @property (nonatomic,copy) NSString *detail; #pragma mark 星级评价 @property (nonatomic,strong) UIImage *rate; @end
弹出详情大头针视图:KCCalloutAnnotatonView.h
// 自定义弹出标注视图 #import <UIKit/UIKit.h> #import <CoreLocation/CoreLocation.h> #import <MapKit/MapKit.h> #import "KCCalloutAnnotation.h"
@interface KCCalloutAnnotationView : MKAnnotationView @property (nonatomic ,strong) KCCalloutAnnotation *annotation; #pragma mark 从缓存取出标注视图 +(instancetype)calloutViewWithMapView:(MKMapView *)mapView; @end
KCCalloutAnnotationView.m
// // KCCalloutView.m // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCCalloutAnnotationView.h" #define kSpacing 5 #define kDetailFontSize 12 #define kViewOffset 80 @interface KCCalloutAnnotationView(){ UIView *_backgroundView; UIImageView *_iconView; UILabel *_detailLabel; UIImageView *_rateView; } @end @implementation KCCalloutAnnotationView -(instancetype)init{ if(self=[super init]){ [self layoutUI]; } return self; } -(instancetype)initWithFrame:(CGRect)frame{ if (self=[super initWithFrame:frame]) { [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; //[_text sizeToFit]; _detailLabel.font=[UIFont systemFontOfSize:kDetailFontSize]; //下方星级 _rateView=[[UIImageView alloc]init]; [self addSubview:_backgroundView]; [self addSubview:_iconView]; [self addSubview:_detailLabel]; [self addSubview:_rateView]; } +(instancetype)calloutViewWithMapView:(MKMapView *)mapView{ static NSString *calloutKey=@"calloutKey1"; KCCalloutAnnotationView *calloutView=(KCCalloutAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:calloutKey]; if (!calloutView) { calloutView=[[KCCalloutAnnotationView alloc]init]; } return calloutView; } #pragma mark 当给大头针视图设置大头针模型时可以在此处根据模型设置视图内容 -(void)setAnnotation:(KCCalloutAnnotation *)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); _rateView.image=annotation.rate; _rateView.frame=CGRectMake(detailX, CGRectGetMaxY(_detailLabel.frame)+kSpacing, annotation.rate.size.width, annotation.rate.size.height); float backgroundWidth=CGRectGetMaxX(_detailLabel.frame)+kSpacing; float backgroundHeight=_iconView.frame.size.height+2*kSpacing; _backgroundView.frame=CGRectMake(0, 0, backgroundWidth, backgroundHeight); self.bounds=CGRectMake(0, 0, backgroundWidth, backgroundHeight+kViewOffset); } @end
地图控件代理方法
#pragma mark 显示大头针时调用,注意方法中的annotation参数是即将显示的大头针对象 -(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{ //由于当前位置的标注也是一个大头针,所以此时需要判断,此代理方法返回nil使用默认大头针视图 if ([annotation isKindOfClass:[KCAnnotation class]]) { static NSString *key1=@"AnnotationKey1"; MKAnnotationView *annotationView=[_mapView dequeueReusableAnnotationViewWithIdentifier:key1]; //如果缓存池中不存在则新建 if (!annotationView) { annotationView=[[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:key1]; // annotationView.canShowCallout=true;//允许交互点击 annotationView.calloutOffset=CGPointMake(0, 1);//定义详情视图偏移量 annotationView.leftCalloutAccessoryView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_classify_cafe.png"]];//定义详情左侧视图 } //修改大头针视图 //重新设置此类大头针视图的大头针模型(因为有可能是从缓存池中取出来的,位置是放到缓存池时的位置) annotationView.annotation=annotation; annotationView.image=((KCAnnotation *)annotation).image;//设置大头针视图的图片 return annotationView; }else if([annotation isKindOfClass:[KCCalloutAnnotation class]]){ //对于作为弹出详情视图的自定义大头针视图无弹出交互功能(canShowCallout=false,这是默认值),在其中可以自由添加其他视图(因为它本身继承于UIView) KCCalloutAnnotationView *calloutView=[KCCalloutAnnotationView calloutViewWithMapView:mapView]; calloutView.annotation=annotation; return calloutView; } else { return nil; } } #pragma mark 选中大头针时触发 //点击一般的大头针KCAnnotation时添加一个大头针作为所点大头针的弹出详情视图 -(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{ KCAnnotation *annotation=view.annotation; if ([view.annotation isKindOfClass:[KCAnnotation class]]) { //点击一个大头针时移除其他弹出详情视图 // [self removeCustomAnnotation]; //添加详情大头针,渲染此大头针视图时将此模型对象赋值给自定义大头针视图完成自动布局 KCCalloutAnnotation *annotation1=[[KCCalloutAnnotation alloc]init]; annotation1.icon=annotation.icon; annotation1.detail=annotation.detail; annotation1.rate=annotation.rate; annotation1.coordinate=view.annotation.coordinate; [mapView addAnnotation:annotation1]; } } #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:[KCCalloutAnnotation class]]) { [_mapView removeAnnotation:obj]; } }]; }
在这个过程中需要注意几点:
1.大头针A作为一个普通大头针,其中最好保存自定义大头针视图C所需要的模型以便根据不同的模型初始化视图。
2.自定义大头针视图C的大头针模型B中不需要title、subtitle属性,最好设置为只读;模型中最后保存自定义大头针视图C所需要的布局模型数据。
3.只有点击非B类大头针时才新增自定义大头针,并且增加时要首先移除其他B类大头针避免重叠(一般建议放到取消大头针选择的代理方法中)。
4.通常在自定义大头针视图C设置大头针模型时布局界面,此时需要注意新增大头针的位置,通常需要偏移一定的距离才能达到理想的效果。
*地图导航
1.系统地图导航
//系统地图进行导航 将起点和终点传递给系统地图
//MKMapItem 地图项目 主要用来表示系统地图上的某个点
//1.设置起点和终点
//设置起点 定位点
MKMapItem *source = [MKMapItem mapItemForCurrentLocation];
//设置终点 输入的地址
//地理编码 人文信息->地理信息
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:self.navTF.text completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (placemarks.count ==0 || error) {
return ;
}
CLPlacemark *clPm = placemarks.lastObject;
MKPlacemark *pm = [[MKPlacemark alloc] initWithPlacemark:clPm];
MKMapItem *dest = [[MKMapItem alloc] initWithPlacemark:pm];
//2.打开系统地图
[MKMapItem openMapsWithItems:@[source, dest] launchOptions:@{MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving, MKLaunchOptionsMapTypeKey: @0}];
}];
扩展:
/**
* MKMapItem 地图项目
//地标对象
@property (nonatomic, readonly) MKPlacemark *placemark;
//地图项目是否在定位点上
@property (nonatomic, readonly) BOOL isCurrentLocation;
//商业名称
@property (nonatomic, copy, nullable) NSString *name;
//电话
@property (nonatomic, copy, nullable) NSString *phoneNumber;
//主页
@property (nonatomic, strong, nullable) NSURL *url;
//时区
@property (nonatomic, copy, nullable) NSTimeZone *timeZone NS_AVAILABLE(10_11, 9_0);
//初始化
+ (MKMapItem *)mapItemForCurrentLocation; //把定位点转化为地图项目
- (instancetype)initWithPlacemark:(MKPlacemark *)placemark; //根据地标对象进行初始化
- (BOOL)openInMapsWithLaunchOptions:(nullable NSDictionary<NSString *, id> *)launchOptions __TVOS_PROHIBITED; //打开系统地图,并且在地图上展示当前的地图项目
+ (BOOL)openMapsWithItems:(NSArray<MKMapItem *> *)mapItems launchOptions:(nullable NSDictionary<NSString *, id> *)launchOptions __TVOS_PROHIBITED; //打开系统地图,并且在传入的地图项目间进行路线导航
@end
MK_EXTERN NSString * const MKLaunchOptionsDirectionsModeKey NS_AVAILABLE(10_9, 6_0) __TVOS_PROHIBITED; // 导航模式
MK_EXTERN NSString * const MKLaunchOptionsMapTypeKey NS_AVAILABLE(10_9, 6_0) __TVOS_PROHIBITED __WATCHOS_PROHIBITED; // 地图类型
MK_EXTERN NSString * const MKLaunchOptionsShowsTrafficKey NS_AVAILABLE(10_9, 6_0) __TVOS_PROHIBITED __WATCHOS_PROHIBITED; // 是否显示交通情况
// 导航模式
MK_EXTERN NSString * const MKLaunchOptionsDirectionsModeDriving NS_AVAILABLE(10_9, 6_0) __TVOS_PROHIBITED; 驾车
MK_EXTERN NSString * const MKLaunchOptionsDirectionsModeWalking NS_AVAILABLE(10_9, 6_0) __TVOS_PROHIBITED; 步行
MK_EXTERN NSString * const MKLaunchOptionsDirectionsModeTransit NS_AVAILABLE(10_11, 9_0) __TVOS_PROHIBITED; 公共交通
// If center and span are present, having a camera as well is undefined
MK_EXTERN NSString * const MKLaunchOptionsMapCenterKey NS_AVAILABLE(10_9, 6_0) __TVOS_PROHIBITED; // 地图中心点
MK_EXTERN NSString * const MKLaunchOptionsMapSpanKey NS_AVAILABLE(10_9, 6_0) __TVOS_PROHIBITED; // 地图跨度
MK_EXTERN NSString * const MKLaunchOptionsCameraKey NS_AVAILABLE(10_10, 7_1) __TVOS_PROHIBITED; // 航拍角度
*/
2.自定义导航
点击开始导航
//自定义地图进行导航 将起点和终点传给服务器
//1.创建导航请求对象
MKDirectionsRequest *requset = [[MKDirectionsRequest alloc] init];
//2.设置起点和终点
requset.source = [MKMapItem mapItemForCurrentLocation];
//地理编码 人文信息->地理信息
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:self.navTF.text completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (placemarks.count ==0 || error) {
return ;
}
CLPlacemark *clPm = placemarks.lastObject;
MKPlacemark *pm = [[MKPlacemark alloc] initWithPlacemark:clPm];
requset.destination = [[MKMapItem alloc] initWithPlacemark:pm];
//3.创建导航对象
MKDirections *directions = [[MKDirections alloc] initWithRequest:requset];
//4.传递数据并计算路线
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
//response 中 包含 NSArray<MKRoute *> *routes 数组<路线对象>
for (MKRoute *route in response.routes) {
//MKRoute 路线对象,包含导航的路线
for (MKRouteStep *step in route.steps) {
//5.取出每一步的数据
NSLog(@"%@", step.instructions);
}
}
}];
}];
扩展:
* MKRoute 路线对象
@property (nonatomic, readonly) NSString *name; 主要道路
@property (nonatomic, readonly) NSArray<NSString *> *advisoryNotices; 建议
@property (nonatomic, readonly) CLLocationDistance distance; 距离
@property (nonatomic, readonly) NSTimeInterval expectedTravelTime; 预计行驶时间
@property (nonatomic, readonly) MKDirectionsTransportType transportType; 交通方式
@property (nonatomic, readonly) MKPolyline *polyline; 折线,路线在地图上展示的线
@property (nonatomic, readonly) NSArray<MKRouteStep *> *steps; 具体的每一步路线
覆盖物添加
- (void)viewDidLoad {
[super viewDidLoad];
self.mgr = [[CLLocationManager alloc] init];
//请求授权
[self.mgr requestWhenInUseAuthorization];
//设置代理 设置覆盖物的展示样式
self.mapView.delegate = self;
}
//自定义地图进行导航 将起点和终点传给服务器
//1.创建导航请求对象
MKDirectionsRequest *requset = [[MKDirectionsRequest alloc] init];
//2.设置起点和终点
requset.source = [MKMapItem mapItemForCurrentLocation];
//地理编码 人文信息->地理信息
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:@"天安门" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (placemarks.count ==0 || error) {
return ;
}
CLPlacemark *clPm = placemarks.lastObject;
MKPlacemark *pm = [[MKPlacemark alloc] initWithPlacemark:clPm];
requset.destination = [[MKMapItem alloc] initWithPlacemark:pm];
//3.创建导航对象
MKDirections *directions = [[MKDirections alloc] initWithRequest:requset];
//4.传递数据并计算路线
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
//response 中 包含 NSArray<MKRoute *> *routes 数组<路线对象>
for (MKRoute *route in response.routes) {
//MKRoute 路线对象,包含导航的路线
//折线属于地图覆盖物的一种,所有遵守<MKOverlay>协议的对象都可以作为覆盖物
//添加地图覆盖物到地图上
[self.mapView addOverlay:route.polyline];
}
}];
}];
实现代理方法:
#pragma mark - MKMapViewDelegate
/**
* 当设置覆盖物的样式时调用
* @param mapView 地图视图
* @param overlay 覆盖物
* @return 覆盖物的样式
*/
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{
//创建折线样式 子类MKPolylineRenderer
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
//设置样式
//线颜色
renderer.strokeColor = [UIColor redColor];
//线宽
renderer.lineWidth = 3;
return renderer;
}
由于定位和地图框架中用到了诸多类,有些初学者容易混淆,下面简单对比一下。
CLLocation:用于表示位置信息,包含地理坐标、海拔等信息,包含在CoreLoaction框架中。
MKUserLocation:一个特殊的大头针,表示用户当前位置。
CLPlacemark:定位框架中地标类,封装了详细的地理信息。
MKPlacemark:类似于CLPlacemark,只是它在MapKit框架中,可以根据CLPlacemark创建MKPlacemark。