0.起步
项目版本有内置地图的开发需求,因此做了一波技术预研。
0.1 MapKit
MapKit是苹果的内置地图框架,目前在国内使用的是高德地图提供的服务,所以即便是内置地图,也能提供较为详细的地图信息。
导入:
#import <MapKit/MapKit.h>
0.2 CoreLocation.framework
CoreLocation是苹果提供的导航+定位服务框架,我们在后续开发中需要依仗他来进行地图导航定位开发。
导入:
#import <CoreLocation/CoreLocation.h>
1.内置地图开发
内置地图开发UI
1.1 MapView
为了实现上图中的地图页面,我们需要通过MapKit提供的MapView来导入地图。
在Capabilities中打开Maps的权限
在StoryBoard中拖入MapView
MapKitView为MapView添加代理,并且指定第一次启动时候加载的地图方位,例如经纬度为(24.489224794270353f,118.18014079685172f)(6号楼的经纬度)
self.mapView.delegate = self;
self.mapView.mapType = MKMapTypeMutedStandard;
self.mapView.showsUserLocation = YES;
self.mapView.userTrackingMode = MKUserTrackingModeFollow;
//CLLocationCoordinate2DMake:参数: 维度、经度、南北方宽度(km)、东西方宽度(km)
double lat = 24.489224794270353f;
double lon = 118.18014079685172f;
[self.mapView setRegion:MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake(lat , lon), 300, 194)
animated:YES];
以上代码中,我们看到self.mapView.showsUserLocation = YES;这一步看字面意思是要在地图上显示用户的地理位置。
但在实际的场景中,MapKit本身不提供导航、定位功能,仅提供地图信息。所以在此我们需要再引入CoreLocation来提供用户的位置信息。
1.2 定位服务
CLLocationManager能够为我们提供导航定位所需的一些用户权限支持,在开启服务之前,我们需要跟用户获取相关的系统权限。
if (nil == _locationManager)
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0){
[_locationManager requestWhenInUseAuthorization];
}
if(![CLLocationManager locationServicesEnabled]){
NSLog(@"请开启定位:设置 > 隐私 > 位置 > 定位服务");
}
// 持续使用定位服务
if([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
[_locationManager requestAlwaysAuthorization]; // 永久授权
[_locationManager requestWhenInUseAuthorization]; //使用中授权
}
// 方位服务
if ([CLLocationManager headingAvailable])
{
_locationManager.headingFilter = 5;
[_locationManager startUpdatingHeading];
}
[_locationManager startUpdatingLocation];
在info.plist中我们需要添加:
Privacy - Location When In Use Usage Description
当我们调用上部分代码后之后,我们便能在地图上看到我们的定位了。
如果一眼看不到,记得拖一拖地图,并且确定Wifi没连接代理VPN。(我曾在洛杉矶看到我的位置)
1.3导航服务
关于导航,我们可以提供的便是我们当前MapKit中的线路绘制,或者调用系统的地图服务app,或者调用百度地图、高德地图这些三方应用。
为了偷懒,我仅仅介绍MapKit绘制地图和调用系统地图App。
系统内置地图导航App:
- (void)navByVender {
CLLocation *begin = [[CLLocation alloc] initWithLatitude:[[NSNumber numberWithFloat:self.myPlace.latitude] floatValue]
longitude:[[NSNumber numberWithFloat:self.myPlace.longitude] floatValue]];
[self.geocoder reverseGeocodeLocation:begin completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
__block CLPlacemark * beginPlace = [placemarks firstObject];
CLLocation *end = [[CLLocation alloc] initWithLatitude:[[NSNumber numberWithFloat:self.finishPlace.latitude] floatValue]
longitude:[[NSNumber numberWithFloat:self.finishPlace.longitude] floatValue]];
[self.geocoder reverseGeocodeLocation:end completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if(error) {
NSLog(@"Error Info %@",error.userInfo);
} else {
CLPlacemark * endPlace = [placemarks firstObject];
MKMapItem * beginItem = [[MKMapItem alloc] initWithPlacemark:beginPlace];
MKMapItem * endItem = [[MKMapItem alloc] initWithPlacemark:endPlace];
NSString * directionsMode;
switch (self.navType) {
case 0:
directionsMode = MKLaunchOptionsDirectionsModeWalking;
break;
case 1:
directionsMode = MKLaunchOptionsDirectionsModeDriving;
break;
case 2:
directionsMode = MKLaunchOptionsDirectionsModeTransit;
break;
default:
directionsMode = MKLaunchOptionsDirectionsModeWalking;
break;
}
NSDictionary *launchDic = @{
//范围
MKLaunchOptionsMapSpanKey : @(50000),
// 设置导航模式参数
MKLaunchOptionsDirectionsModeKey : directionsMode,
// 设置地图类型
MKLaunchOptionsMapTypeKey : @(MKMapTypeStandard),
// 设置是否显示交通
MKLaunchOptionsShowsTrafficKey : @(YES),
};
[MKMapItem openMapsWithItems:@[beginItem, endItem] launchOptions:launchDic];
}
}];
}];
}
导航发起之前,我们需要准备好两个坐标,以上代码中,我把用户自身的地址作为Begin地点,把地图正中央作为目的地的坐标进行导航。(反正你传两个坐标就对了)
//地理编码方法
- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;
// 反地理编码方法
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;
地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
反地理编码:根据给定的经纬度,获得具体的位置信息
我们需要reverseGeocodeLocation来做地图的反地理编码操作,这样我们传入的地理坐标才被识别为地理位置信息。
[MKMapItem openMapsWithItems:@[beginItem, endItem] launchOptions:launchDic];
这一处代码,变回唤起系统内置的地图导航功能。
内置MapKit可绘制的导航方案:
MKPlacemark *fromPlacemark = [[MKPlacemark alloc] initWithCoordinate:self.myPlace addressDictionary:nil];
MKPlacemark *toPlacemark = [[MKPlacemark alloc] initWithCoordinate:self.finishPlace addressDictionary:nil];
MKMapItem *fromItem = [[MKMapItem alloc] initWithPlacemark:fromPlacemark];
MKMapItem *toItem = [[MKMapItem alloc] initWithPlacemark:toPlacemark];
- (void)findDirectionsFrom:(MKMapItem *)from to:(MKMapItem *)to{
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
request.source = from;
request.destination = to;
request.transportType = MKDirectionsTransportTypeWalking;
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
//ios7获取绘制路线的路径方法
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
if (error) {
NSLog(@"Error info:%@", error.userInfo[@"NSLocalizedFailureReason"]);
}
else {
for (MKRoute *route in response.routes) {
// MKRoute *route = response.routes[0];
for(id<MKOverlay> overLay in self.mapView.overlays) {
[self.mapView removeOverlay:overLay];
}
[self.mapView addOverlay:route.polyline level:0];
double lat = self.mapView.region.center.latitude;
double lon = self.mapView.region.center.longitude;
double latDelta = self.mapView.region.span.latitudeDelta * 100000;
double lonDelta = self.mapView.region.span.longitudeDelta * 100000;
if(_firstStarNav) {
_firstStarNav = NO;
[self.mapView setRegion:MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake(lat , lon), 200, 126)
animated:YES];
}
}
}
}];
}
在以上方法后,我们可以在以下一个代理中获得一套地图路线,我们可以通过以下方式,将绘制到地图上的线路定制化。
- (MKOverlayRenderer*)mapView:(MKMapView*)mapView rendererForOverlay:(id)overlay {
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
renderer.lineWidth = 5;
renderer.strokeColor = HEX_RGBA(0xf26f5f, 1);
return renderer;
}
这里补充一下,在地图上显示的各种线段绘制之类的呃,都是要在overlay层进行表示的。
我们可控制的线段的宽度、颜色、延续的拐角光滑度、线头是否圆角。
1.4 地图中的元素定制
在地图中我们可以对一些UI方案进行定制。
内置地图开发UI
我们能够进行完全定制的,是在地图上的Pin图钉。
我们可以在一下方法中,对Annotation进行修改。(这个方法堪比 table的那个cellForRow)
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
在图钉上方弹出的苹果称之为CalloutAccessoryView,这里我们可以修改的便是左右部分的View,此处可以添加Button或者UIImageView。
我们在这个方法中,还会获取到用户个人定位服务下自己的图钉信息。此处也可以定制。
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView* aView;
if ([annotation isKindOfClass:[MKUserLocation class]]) {
self.myPlace = annotation.coordinate;
return nil;
} else if([annotation isKindOfClass:[MyPinAnnotation class]]) {
aView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"MyPinAnnotation"];
aView.canShowCallout = YES;
aView.image = [UIImage imageNamed:@"pin"];
aView.frame = CGRectMake(0, 0, 50, 50);
UIImageView *myCustomImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon"]];
myCustomImage.frame = CGRectMake(0, 0, 50, 50);
aView.leftCalloutAccessoryView = myCustomImage;
MapMarkBtn *rightButton = [[MapMarkBtn alloc] initWithFrame:CGRectMake(0, 0, 80, 50)];
rightButton.coordinate = annotation.coordinate;
rightButton.backgroundColor = [UIColor grayColor];
[rightButton setTitle:@"到这里去" forState:UIControlStateNormal];
[rightButton addTarget:self action:@selector(gotoPlace:) forControlEvents:UIControlEventTouchUpInside];
aView.rightCalloutAccessoryView = rightButton;
}
else {
aView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"MKPointAnnotation"];
aView.canShowCallout = YES;
aView.image = [UIImage imageNamed:@"pin"];
aView.frame = CGRectMake(0, 0, 50, 50);
}
return aView;
}
多说无益,附上地图功能的Demo地址:
祝:各位看官身体健康
此致敬礼!