Map
地图
- CoreLocation :用于地理定位,地理编码,区域监听等(着重功能实现)
- MapKit :用于地图展示,例如大头针,路线、覆盖层展示等(着重界面展示)
2个热门专业术语
- LBS :Location Based Service
- SoLoMo :Social Local Mobile(索罗门)
Core Location
CLLocationManager 定位
- 需要在info.plist加入key“Privacy - Location Usage Description”,和对应value的描述
- 初始化设置
// 创建位置管理者 _manager = [[CLLocationManager alloc] init]; // 设置代理 self.manager.delegate = self; // 每隔多少米定位一次 _manager.distanceFilter = 100; /** kCLLocationAccuracyBestForNavigation // 最适合导航 kCLLocationAccuracyBest; // 最好的 kCLLocationAccuracyNearestTenMeters; // 10m kCLLocationAccuracyHundredMeters; // 100m kCLLocationAccuracyKilometer; // 1000m kCLLocationAccuracyThreeKilometers; // 3000m */ // 精确度越高, 越耗电, 定位时间越长 self.manager.desiredAccuracy = kCLLocationAccuracyBest; }- 版本适配
/** -------iOS8.0+定位适配-------- */
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0 ) {
// 前台定位授权(默认情况下,不可以在后台获取位置, 勾选后台模式 location update, 但是 会出现蓝条)
// [_lM requestWhenInUseAuthorization];
// 前后台定位授权(请求永久授权)
// +authorizationStatus != kCLAuthorizationStatusNotDetermined
// 这个方法不会有效
// 当前的授权状态为前台授权时,此方法也会有效
[_manager requestAlwaysAuthorization];
}
// 允许后台获取用户位置(iOS9.0)
if([[UIDevice currentDevice].systemVersion floatValue] >= 9.0)
{
// 一定要勾选后台模式 location updates
_manager.allowsBackgroundLocationUpdates = YES;
- 开始更新定位
// 需要时,勾选后台模式 location updates
[self.manager startUpdatingLocation];
- 遵守代理协议CLLocationManagerDelegate,实现回调方法
/** 更新到位置后调用 */
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
[self.manager stopUpdatingLocation];
}
/** 授权状态发生改变时调用 */
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
switch (status) {
case kCLAuthorizationStatusNotDetermined:
NSLog(@"用户未决定");
break;
case kCLAuthorizationStatusRestricted:
NSLog(@"访问受限制");
break;
case kCLAuthorizationStatusDenied:
// 定位是否可用(是否支持定位或者定位是否开启)
if ([CLLocationManager locationServicesEnabled]) {
NSLog(@"定位开启,但被拒");
}else
{
NSLog(@"定位关闭");
}
break;
case kCLAuthorizationStatusAuthorizedAlways:
NSLog(@"获取前后台定位");
break;
case kCLAuthorizationStatusAuthorizedWhenInUse:
NSLog(@"获取前后台定位");
break;
default:
break;
}
}
/** 定位失败 */
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
- CLLocation
// CLLocation用来表示某个位置的地理信息,比如经纬度、海拔等等
@property(readonly, nonatomic) CLLocationCoordinate2D coordinate;
经纬度
@property(readonly, nonatomic) CLLocationDistance altitude;
// 海拔
@property(readonly, nonatomic) CLLocationDirection course;
// 路线,航向(取值范围是0.0° ~ 359.9°,0.0°代表真北方向)
@property(readonly, nonatomic) CLLocationSpeed speed;
// 移动速度(单位是m/s)
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
// 方法可以计算2个位置之间的距离
- 场景演示:打印当前用户的行走方向,偏离角度以及对应的行走距离
// 老位置
static CLLocation *_oldL;
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
CLLocation *location = [locations lastObject];
// 1. 获取方向偏向
NSString *angleStr = nil;
switch ((int)location.course / 90) {
case 0:
angleStr = @"北偏东";
break;
case 1:
angleStr = @"东偏南";
break;
case 2:
angleStr = @"南偏西";
break;
case 3:
angleStr = @"西偏北";
break;
default:
angleStr = @"跑沟里去了!!";
break;
}
// 2. 偏向角度
NSInteger angle = 0;
angle = (int)location.course % 90;
// 代表正方向
if (angle == 0) {
NSRange range = NSMakeRange(0, 1);
angleStr = [NSString stringWithFormat:@"正%@", [angleStr substringWithRange:range]];
}
// 3.移动多少米
double distance = 0;
if(_oldL)
{
distance = [location distanceFromLocation:_oldL];
}
_oldL = location;
// 4. 拼串 打印
// 例如:”北偏东 30度 方向,移动了8米”
NSString *noticeStr = [NSString stringWithFormat:@"%@%zd方向, 移动了%f米", angleStr, angle, distance];
NSLog(@"%@", noticeStr);
}
指南针的实现
CLLocationManager manager = [[CLLocationManager alloc] init];
_manager.delegate = self;
// 采样间隔(度)
_manager.headingFilter = 2;
// 开始监听手机朝向改变
[self.manager startUpdatingHeading];
/**
* 获取到手机朝向时调用
* @param manager 位置管理者
* @param newHeading 朝向对象
*/
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
/**
* CLHeading
* magneticHeading : 磁北角度
* trueHeading : 真北角度
*/
CGFloat angle = newHeading.magneticHeading / 180 * M_PI;
[UIView animateWithDuration:0.2 animations:^{
self.imageView.transform = CGAffineTransformMakeRotation(-angle);
}];
}
区域监听
CLLocationManager *manager = [[CLLocationManager alloc] init];
_manager.delegate = self;
if([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)
{
[_manager requestAlwaysAuthorization];
}
// 开启区域监听 这个方法iOS 7 已过期
CLLocationCoordinate2D center = {31.23, 133.451};
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:1000 identifier:@"sinalme"];
// 请求区域状态
[self.lM requestStateForRegion:region];
// 实现代理方法
/** 进入区域 */
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(@"进入区域--%@", region.identifier);
}
/** 离开区域 */
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(@"离开区域--%@", region.identifier);
}
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSLog(@"%zd", state);
}
(反)地理编码
- 地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
- CLPlacemark
CLPlacemark的字面意思是地标,封装详细的地址位置信息
@property (nonatomic, readonly) CLLocation *location;
地理位置
@property (nonatomic, readonly) CLRegion *region;
区域
@property (nonatomic, readonly) NSDictionary *addressDictionary;
详细的地址信息
@property (nonatomic, readonly) NSString *name;
地址名称
@property (nonatomic, readonly) NSString *locality;
城市
- CLGeocoder
NSString *address = self.textView.text;
if (address.length == 0) {
return;
}
[self.geocoder geocodeAddressString:@"中山" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (error == nil) {
[placemarks enumerateObjectsUsingBlock:^(CLPlacemark * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
self.textView.text = obj.name;
self.LontextField.text = @(obj.location.coordinate.longitude).stringValue;
self.latTextField.text = @(obj.location.coordinate.latitude).stringValue;
}];
}else
{
NSLog(@"mistake-%@",error.localizedDescription);
}
}];
- 反地理编码:根据给定的经纬度,获得具体的位置信息
double lon = self.LontextField.text.doubleValue;
double lat = self.latTextField.text.doubleValue;
CLLocation *location = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
[self.geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (!error) {
[placemarks enumerateObjectsUsingBlock:^(CLPlacemark * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
self.textView.text = obj.name;
self.LontextField.text = @(obj.location.coordinate.longitude).stringValue;
self.latTextField.text = @(obj.location.coordinate.latitude).stringValue;
}];
}else
{
NSLog(@"%@",error.localizedDescription);
}
}];
第三方框架 LocationManager
- iOS 8
- NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription
- in your app's Info.plist 添加描述
INTULocationManager *locMgr = [INTULocationManager sharedInstance];
[locMgr requestLocationWithDesiredAccuracy:INTULocationAccuracyCity timeout:10.0 delayUntilAuthorized:YES block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) {
if (status == INTULocationStatusSuccess) {
NSLog(@"%@",currentLocation);
}
else if (status == INTULocationStatusTimedOut) {
NSLog(@"超时");
}
else {
NSLog(@"未知");
}
}];
MapKit
- 添加 MapKit.framework
- 导入头文件 MapKit/MapKit.h
- 添加 MKMapView
- 创建 CLLocationManager 对象
- 判断是否授权
if ([_lm respondsToSelector:@selector(requestAlwaysAuthorization)]) {
[_lm requestAlwaysAuthorization];
}
- 设置mapView相关属性
self.mapView.showsUserLocation = YES;
self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
- 遵守 MKMapViewDelegate 代理协议,实现协议方法
/**
* 更新到位置
*
* @param mapView 地图
* @param userLocation 位置对象
*/
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
/** MKUserLocation (大头针模型) */
userLocation.title = @"";
userLocation.subtitle = @"";
// 设置地图显示中心
[self.mapView setCenterCoordinate:userLocation.location.coordinate animated:YES];
// 设置地图显示区域
MKCoordinateSpan span = MKCoordinateSpanMake(0.051109, 0.034153);
MKCoordinateRegion region = MKCoordinateRegionMake(userLocation.location.coordinate, span);
[self.mapView setRegion:region animated:YES];
}
大头针的实现
- 加MapKit.framework,导入MapKit/MapKit.h,info中添加key Privacy - Location Always Usage Description
- 创建mapView,获取屏幕触摸点,转换成经纬度
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CGPoint point = [[touches anyObject] locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
// 添加大头针
[self addAnnoWithCoordinate:coordinate];
}
- 自定义大头针
#import <MapKit/MapKit.h>
@interface WZAnno : NSObject <MKAnnotation>
@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy, nullable) NSString *title;
@property (nonatomic, copy, nullable) NSString *subtitle;
@property (nonatomic,assign) NSInteger type;
@end
- 添加大头针
- (void)addAnnoWithCoordinate:(CLLocationCoordinate2D)coordinate
{
// 创建标签
__block WZAnno *anno = [[WZAnno alloc] init];
anno.title = @"中国山东";
anno.subtitle = @"找蓝翔";
anno.coordinate = coordinate;
anno.type = arc4random_uniform(5);
// 给mapView添加标签
[self.mapView addAnnotation:anno];
// 获取位置
CLLocation *location = [[CLLocation alloc] initWithLatitude:anno.coordinate.latitude longitude:anno.coordinate.longitude];
// 反地理编码
[self.geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
CLPlacemark *placemark = [placemarks firstObject];
anno.title = placemark.locality;
anno.subtitle = placemark.thoroughfare;
}];
}
- 设置mapView代理,遵守MKMapViewDelegate协议,实现回调方法,设置大头针弹出的泡泡view相关属性
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
// 循环利用大头针弹出的泡泡view
static NSString *ID = @"annoView"; // 标识符
MKAnnotationView *annoV = [mapView dequeueReusableAnnotationViewWithIdentifier:ID];
if (annoV == nil) {
annoV = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ID];
}
// 防止循环利用或者annoV有值,需要重新赋值
annoV.annotation = annotation;
// 拖动
annoV.draggable = YES;
// 是否弹出标注
annoV.canShowCallout = YES;
// 设置不同类型的大头针图片
WZAnno *anno = (WZAnno *)annotation;
NSString *iamgeName = [NSString stringWithFormat:@"category_%zd",anno.type];
annoV.image = [UIImage imageNamed:iamgeName];
// 泡泡view左右添加imageView
UIImageView *lImageV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
lImageV.image = [UIImage imageNamed:@"htl"];
annoV.leftCalloutAccessoryView = lImageV;
UIImageView *rImageV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
rImageV.image = [UIImage imageNamed:@"eason"];
annoV.rightCalloutAccessoryView = rImageV;
// 泡泡view中间添加imageView
annoV.detailCalloutAccessoryView = [[UISwitch alloc] init];
return annoV;
}
/** 选中大头针 */
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view;
/** 取消选中大头针 */
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view;
地图快照
// 地图快照的设置对象
MKMapSnapshotOptions *option = [[MKMapSnapshotOptions alloc] init];
option.size = CGSizeMake(1000, 2000);
option.scale = [UIScreen mainScreen].scale;
option.region = self.mapView.region;
option.showsBuildings = YES;
// 创建地图快照对象
MKMapSnapshotter *snap = [[MKMapSnapshotter alloc] initWithOptions:option];
// 快照回调
[snap startWithCompletionHandler:^(MKMapSnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (error == nil) {
UIImage *image = snapshot.image;
NSData *data = UIImagePNGRepresentation(image);
[data writeToFile:@"users/apple/desktop/snapImage2.png" atomically:YES];
}
}];
导航
// 通过地理编码拿到起点和终点
[self.geocoder geocodeAddressString:@"中山" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
CLPlacemark *beginP = [placemarks firstObject];
[self.geocoder geocodeAddressString:@"上饶" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
CLPlacemark *endP = [placemarks firstObject];
[self startNaviWithBeganPlacemark:beginP endPlacemark:endP];
}];
}];
// 起点
MKPlacemark *bePlacemark = [[MKPlacemark alloc] initWithPlacemark:beginP];
MKMapItem *beginItem = [[MKMapItem alloc] initWithPlacemark:bePlacemark];
// 终点
MKPlacemark *endPlacemark = [[MKPlacemark alloc] initWithPlacemark:endP];
MKMapItem *endItem = [[MKMapItem alloc] initWithPlacemark:endPlacemark];
NSArray *items = @[beginItem,endItem];
NSDictionary *dict = @{
MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving,
MKLaunchOptionsMapTypeKey : @(MKMapTypeHybrid),
MKLaunchOptionsShowsTrafficKey : @(YES)
};
// 开始导航
[MKMapItem openMapsWithItems:items launchOptions:dict];
获取导航信息、绘制导航路线
// 给起点和终点添加一个圆形覆盖层
MKCircle *beginCircle = [MKCircle circleWithCenterCoordinate:beginP.location.coordinate radius:100000];
[self.mapView addOverlay:beginCircle];
MKCircle *endCircle = [MKCircle circleWithCenterCoordinate:endP.location.coordinate radius:100000];
[self.mapView addOverlay:endCircle];
// 创建请求
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
// 添加起点和终点
MKPlacemark *bePlacemark = [[MKPlacemark alloc] initWithPlacemark:beginP];
MKMapItem *sourceItem = [[MKMapItem alloc] initWithPlacemark:bePlacemark];
request.source = sourceItem;
MKPlacemark *endPlacemark = [[MKPlacemark alloc] initWithPlacemark:endP];
MKMapItem *destinationItem = [[MKMapItem alloc] initWithPlacemark:endPlacemark];
request.destination = destinationItem;
MKDirections *direction = [[MKDirections alloc] initWithRequest:request];
// 遍历导航信息
[direction calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
/**
* MKDirectionsResponse
routes : 路线数组MKRoute
*/
/**
* MKRoute
name : 路线名称
distance : 距离
expectedTravelTime : 预期时间
polyline : 折线(数据模型)
steps
*/
/**
* steps <MKRouteStep *>
instructions : 行走提示
*/
[response.routes enumerateObjectsUsingBlock:^(MKRoute * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@ - %f - %f",obj.name,obj.expectedTravelTime,obj.distance);
MKPolyline *polyline = obj.polyline;
// 给路线添加一个覆盖层数据模型
[self.mapView addOverlay:polyline];
[obj.steps enumerateObjectsUsingBlock:^(MKRouteStep * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@",obj.instructions);
}];
}];
}];
- 实现mapView代理方法
/** 获取对应的图层渲染,返回渲染图层 */
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKCircle class]]) {
MKCircleRenderer *render = [[MKCircleRenderer alloc] initWithOverlay:overlay];
render.fillColor = [UIColor cyanColor];
render.alpha = 0.5;
return render;
}
if ([overlay isKindOfClass:[MKPolyline class]]) {
MKPolylineRenderer *lineRender = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
lineRender.strokeColor = [UIColor redColor];
lineRender.lineWidth = 10;
return lineRender;
}
return nil;
}