天天看点

iOS内置地图导航开发指南

0.起步 项目版本有内置地图的开发需求,因此做了一波技术预研。 0.1 MapKit MapKit是苹果的内置地图框架,目前在国内使用的是高德地图提供的服务,所以即便是内置地图,也能提供较为详细的地图信息。 导入:

#import <MapKit/MapKit.h>
复制代码
           

0.2 CoreLocation.framework CoreLocation是苹果提供的导航+定位服务框架,我们在后续开发中需要依仗他来进行地图导航定位开发。 导入:

#import <CoreLocation/CoreLocation.h>
复制代码
           

1.内置地图开发

1.1 MapView 为了实现上图中的地图页面,我们需要通过MapKit提供的MapView来导入地图。 在Capabilities中打开Maps的权限

在StoryBoard中拖入MapView

为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方案进行定制。

我们能够进行完全定制的,是在地图上的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地址:

https://github.com/filelife/FLMapKit.git
复制代码
           

祝:各位看官身体健康 此致敬礼!