首先看一下Demo的整體實現效果
Demo實現的幾大塊技能點:
1.地圖展示線路、氣泡展示點詳情
2.語音合成部分
3.後臺定位,計算點之間距離判斷是否播報語音
下面分段講述每個部分東西:
1.地圖展示線路部分
其實做過地圖方面應用的程序猿們應該都知道怎麼做。本demo沒有集成其他地圖,用的是系統的地圖,所以有點坑需要填。畫線部分就不做贅述,基本上都是用MKPolyline畫線並作爲overlay添加進mapView裏面,如果不會的話,網上百度會有一大堆文章介紹怎麼去畫線的。這裏面唯一有點坑的就是,標註大頭針還需要同時展示氣泡這個操作,如下面的這個樣式。
正常的我們在地圖上展示氣泡都不會同時展示出氣泡中的內容的,所以這個就需要我們重寫這個方法。需要重寫兩個類,一個是繼承MKAnnotation的類:
.h文件:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface MyAnnotation : NSObject<MKAnnotation>
//顯示標註的經緯度
@property (nonatomic,readonly) CLLocationCoordinate2D coordinate;
//標註的標題
@property (nonatomic,copy,readonly) NSString * title;
//標註的子標題
@property (nonatomic,copy,readonly) NSString * subtitle;
-(id)initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates title:(NSString *)paramTitle
subTitle:(NSString *)paramTitle;
@end
.m文件
#import "MyAnnotation.h"
@implementation MyAnnotation
-(id)initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates title:(NSString *)paramTitle
subTitle:(NSString *)paramSubitle
{
self = [super init];
if(self != nil)
{
_coordinate = paramCoordinates;
_title = paramTitle;
_subtitle = paramSubitle;
}
return self;
}
@end
另一個就是重寫一個繼承MKAnnotationView的類
.h文件:
#import <MapKit/MapKit.h>
#import "MyAnnotation.h"
@interface NewAnnotation : MKAnnotationView
@property (nonatomic,strong) UILabel *label;
@end
.m文件:
#import "NewAnnotation.h"
@implementation NewAnnotation
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
// 在大頭針旁邊加一個label
self.label = [[UILabel alloc]initWithFrame:CGRectMake(-40,-40,80, 20)];
self.label.text = annotation.title;
self.label.font = [UIFont systemFontOfSize:11];
self.label.textColor = [UIColor orangeColor];
self.label.backgroundColor = [UIColor whiteColor];
self.label.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.label];
UIImageView *myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(-10,-20, 20, 26)];
myImageView.image = [UIImage imageNamed:@"bubble_icon"];
[self addSubview:myImageView];
}
return self;
}
@end
創建氣泡也就不用說,就像下面這麼簡單就OK了
CLLocationCoordinate2D coor = CLLocationCoordinate2DMake([lat_lonArray[1] doubleValue], [lat_lonArray[0] doubleValue]);
MyAnnotation * myAnnotation = [[MyAnnotation alloc] initWithCoordinates:coor title:[NSString stringWithFormat:@"%d.%@",j+1,model.name] subTitle:@""];
[weakSelf.mapView addAnnotation:myAnnotation];
代理方法:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKAnnotationView * result = nil;
if([annotation isKindOfClass:[MKUserLocation class]])
{
MKAnnotationView *iAnnotation=[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"annotation2"];
iAnnotation.image = [UIImage imageNamed:@"car_icon"];
iAnnotation.canShowCallout=NO;
return iAnnotation;
}
if([mapView isEqual:self.mapView] == NO)
{
return result;
}
NewAnnotation *annotationView =
annotationView = [[NewAnnotation alloc]initWithAnnotation:annotation reuseIdentifier:@"otherAnnotationView"];
return annotationView;
}
畫線標打頭陣、氣泡這樣就結束了。
2.語音合成部分
語音合成部分,我使用的是百度語音合成,雖然聲音聽上去沒有訊飛語音那個美女聲音好聽,但是百度語音不管在線還是離線語音都可以用,每天免費的次數還多啊 ,不選這選啥呀。
百度語音合成怎麼集成進項目裏面,就不說了,畢竟人家百度寫的已經很詳細了,沒有可贅述的。需要注意的是,因爲這個demo需要做到後臺定位和後臺語音播報的功能,所以必須去修改下面的東西:
至於語音播報使用到麥克風和地圖定位使用到定位權限的白名單設置問題,也不贅述了,這個都是比較簡單的東西。
3.後臺定位,計算點之間距離判斷是否播報語音
這個藍條,是app使用後臺定位的效果,最主要的一句話就是下面這句:
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) {
self .locationManager.allowsBackgroundLocationUpdates = YES;
}
這句 self .locationManager.allowsBackgroundLocationUpdates = YES;是iOS9.0之後的API,需要判斷下手機系統,如果是之前的系統,強行使用這句話的話,就會直接崩潰。
後臺定位是使用CLLocationManagerDelegate更新位置的代理方法,而不是走MKMapView更新位置的代理方法。即
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
而不是下面這個方法:
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
但是CLLocationManagerDelegate更新位置的代理方法有點坑,定位的經緯度是國外的地圖的經緯度,與中國的經緯度有區別,偏差較大。所以需要轉換成國內的經緯度,即火星座標。
網上有很多將系統地圖經緯度轉換成火星座標敬畏的文章,下面貼出我用的這個:
.h文件
//
// CLLocation+Sino.h
//
// Created by [email protected] on 13-4-26.
// 火星座標系轉換擴展
//
// earth(國外 WGS84), mars(國內 GCJ-02), bearPaw(百度 BD-09) 座標系間相互轉換
// 未包含 mars2earth. 需要這個可參考 http://xcodev.com/131.html
#import <CoreLocation/CoreLocation.h>
@interface CLLocation (Sino)
- (CLLocation*)locationMarsFromEarth;
//- (CLLocation*)locationEarthFromMars; // 未實現
- (CLLocation*)locationBearPawFromMars;
- (CLLocation*)locationMarsFromBearPaw;
@end
.m文件:
#import "CLLocation+Sino.h"
void transform_earth_2_mars(double lat, double lng, double* tarLat, double* tarLng);
void transform_mars_2_bear_paw(double lat, double lng, double* tarLat, double* tarLng);
void transform_bear_paw_2_mars(double lat, double lng, double* tarLat, double* tarLng);
@implementation CLLocation (Sino)
- (CLLocation*)locationMarsFromEarth;
{
double lat = 0.0;
double lng = 0.0;
transform_earth_2_mars(self.coordinate.latitude, self.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat, lng)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
}
- (CLLocation*)locationEarthFromMars;
{
// 二分法查糾偏文件
// http://xcodev.com/131.html
return nil;
}
- (CLLocation*)locationBearPawFromMars;
{
double lat = 0.0;
double lng = 0.0;
transform_mars_2_bear_paw(self.coordinate.latitude, self.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat, lng)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
}
- (CLLocation*)locationMarsFromBearPaw;
{
double lat = 0.0;
double lng = 0.0;
transform_bear_paw_2_mars(self.coordinate.latitude, self.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat, lng)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
}
@end
// --- transform_earth_2_mars ---
使用的時候如下面就可以了
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
CLLocation *newLocation = locations.lastObject;
newLocation = [newLocation locationMarsFromEarth];//將位置轉換成中國地圖
}
整個demo基本上就這些可以說的點,最後需要贅述一下,如果你的項目裏面需要使用這樣的功能,在上傳App Store的時候,你需要說明清楚你在哪個地方使用後臺定位、後臺語音播報功能,爲什麼要使用,之後還需要你錄製相關應用的視頻(放在優酷網就可以了)。因爲比較消耗系統性能,尤其電量,所以審覈還是比較嚴格的,就算被打回來了,根據他的反饋針對性解釋清楚,最後還是可以上線的。
最後,如果文章有什麼說的不對的地方還請大家不吝賜教,相互學習,謝謝🙏。