iOS內建地圖導航開發指南

VincentJac發表於2017-12-21

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地址:

https://github.com/filelife/FLMapKit.git
複製程式碼

iOS內建地圖導航開發指南

祝:各位看官身體健康 此致敬禮!

相關文章