地图与定位(二)系统地图

iOS从6.0开始地图服务不再由谷歌驱动,而是改用自家地图,当然在国内它的数据是由高德地图提供的。这样一来,如果在iOS6.0之前进行地图开发的话使用方法会有所不同,基于目前的情况其实使用iOS6.0之前版本的系统基本已经寥寥无几了,所有在接下来的内容中不会再针对iOS5及之前版本的地图开发进行介绍。

在iOS中进行地图开发主要有两种方式,一种是直接利用MapKit框架进行地图开发,利用这种方式可以对地图进行精准的控制;另一种方式是直接调用苹果官方自带的地图应用,主要用于一些简单的地图应用(例如:进行导航覆盖物填充等),无法进行精确的控制。当然,本节重点内容还是前者,系统地图的使用会在后面讲到。


1. 使用MapView显示地图

用MapKit之前需要简单了解一下,MapKit中的类都是以MK开头的,其中用于地图展示的UI控件是MKMapView,下面是MKMapView一些常用属性和方法,具体如下表:

属性 说明
userTrackingMode 跟踪用户当前位置的类型,是一个枚举:MKUserTrackingModeNone :不进行用户位置跟踪;MKUserTrackingModeFollow :跟踪用户位置;MKUserTrackingModeFollowWithHeading :跟踪用户位置并且跟踪用户前进方向;
mapType 地图类型,是一个枚举:MKMapTypeStandard :标准地图,一般情况下使用此地图即可满足;MKMapTypeSatellite :卫星地图;MKMapTypeHybrid :混合地图,加载最慢比较消耗资源;MKMapTypeSatelliteFlyover:3D立体卫星MKMapTypeHybridFlyover:SD立体混合
userLocation 用户位置,只读属性
annotations 当前地图中的所有大头针,只读属性
对象方法 说明
-(void)addAnnotation:(id )annotation; 添加大头针,对应的有添加大头针数组
-(void)removeAnnotation:(id )annotation; 删除大头针,对应的有删除大头针数组
-(void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated; 设置地图显示区域,用于控制当前屏幕显示地图范围
-(void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated; 设置地图中心点位置
-(CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view; 将地理坐标(经纬度)转化为数学坐标(UIKit坐标)
-(CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view; 将数学坐标转换为地理坐标
-(MKAnnotationView )dequeueReusableAnnotationViewWithIdentifier:(NSString )identifier; 从缓存池中取出大头针,类似于UITableView中取出UITableViewCell,为了进行性能优化而设计
-(void)selectAnnotation:(id )annotation animated:(BOOL)animated; 选中指定的大头针
-(void)deselectAnnotation:(id )annotation animated:(BOOL)animated; 取消选中指定的大头针
代理方法 说明
-(void)mapView:(MKMapView )mapView didUpdateUserLocation:(MKUserLocation )userLocation ; 用户位置发生改变时触发(第一次定位到用户位置也会触发该方法,会频繁触发该方法,并反馈用户的位置信息)
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated ; 显示区域发生改变后触发
-(void)mapViewDidFinishLoadingMap:(MKMapView *)mapView; 地图加载完成后触发
-(MKAnnotationView )mapView:(MKMapView )mapView viewForAnnotation:(id )annotation; 显示大头针时触发,返回大头针视图,通常自定义大头针可以通过此方法进行
-(void)mapView:(MKMapView )mapView didSelectAnnotationView:(MKAnnotationView )view 点击选中某个大头针时触发
-(void)mapView:(MKMapView )mapView didDeselectAnnotationView:(MKAnnotationView )view 取消选中大头针时触发
-(MKOverlayRenderer )mapView:(MKMapView )mapView rendererForOverlay:(id )overlay 渲染地图覆盖物时触发

我们先来看一下如何在地图上实现用户位置跟踪,在很多带有地图的应用中默认打开地图都会显示用户当前位置,同时将当前位置标记出来放到屏幕中点方便用户对周围情况进行查看。如果在iOS6或者iOS7中实现这个功能只需要添加地图控件、设置用户跟踪模式、在-(void)mapView:(MKMapView )mapView didUpdateUserLocation:(MKUserLocation )userLocation代理方法中设置地图中心区域及显示范围。但是在iOS8中用法稍有不同:

  • 由于在地图中进行用户位置跟踪需要使用定位功能,而定位功能在iOS8中设计发生了变化,因此必须按照前面定位章节中提到的内容进行配置和请求。
  • iOS8中不需要进行中心点的指定,默认会将当前位置设置中心点并自动设置显示区域范围。

了解以上两点,要进行用户位置跟踪其实就相当简单了,值得一提的是-(void)mapView:(MKMapView )mapView didUpdateUserLocation:(MKUserLocation )userLocation这个代理方法。这个方法只有在定位(利用前面章节中的定位内容)到当前位置之后就会调用,以后每当用户位置发生改变就会触发,调用频率相当频繁。下面是实现用户位置追踪的代码:

示例代码

#import "ViewController.h"
#import 
#import 

@interface ViewController ()<CLLocationManagerDelegate,MKMapViewDelegate>

@property (nonatomic, strong)CLLocationManager *locationManager;
@property (nonatomic, strong)MKMapView *mapView;

@end

@implementation ViewController

// 定位管家
- (CLLocationManager *)locationManager {

    if (!_locationManager) {

        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
    }
    return _locationManager;
}

// 地图视图
- (MKMapView *)mapView {
    if (!_mapView) {
        _mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
        _mapView.delegate = self;
    }
    return _mapView;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    // 1.判断用户设备定位功能是否开启
    if (![CLLocationManager locationServicesEnabled]) {
        NSLog(@"提醒用户定位服务未开启");
    }

    // 2.判断应用的授权状态,请求用户定位服务授权
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {

        // iOS8以上的版本才需要获取定位授权
        if ([[[UIDevice currentDevice]  systemVersion] floatValue] >= 8.0) {
            //调用此方法之前必须在plist文件中添加NSLocationWhenInUseUsageDescription --string-- 后面跟的文字就是提示信息
            [self.locationManager requestWhenInUseAuthorization];
        }
    }

    // 3.添加地图到界面
    [self.view addSubview:self.mapView];
    [self.view sendSubviewToBack:self.mapView];

    // 4.设置地图的常用属性
    self.mapView.mapType = MKMapTypeStandard;// 设置地图类型
    self.mapView.userTrackingMode = MKUserTrackingModeFollow;// 设置用户追踪模式,不设置设置追踪模式地图不会随用户移动

    // 地图的操作属性
    // 设置对应的属性时,注意该属性是从哪个系统版本开始引入的,做好不同系统版本的适配
    self.mapView.zoomEnabled = YES;// 是否可以缩放
    self.mapView.scrollEnabled = YES;// 是否可以滚动
    self.mapView.rotateEnabled = YES;// 是否可以旋转
    self.mapView.pitchEnabled = YES;// 是否显示3D

    // 地图的显示属性
    self.mapView.showsCompass = YES;// 是否显示指南针
    self.mapView.showsScale = YES;// shif显示比例尺
    self.mapView.showsTraffic = YES;// 是否显示交通状况
    self.mapView.showsBuildings = YES;// 是否显示建筑物
    self.mapView.showsUserLocation = YES;// 是否显示用户的位置:会在地图显示一个蓝色的圆点,标记当前位置,
}

#pragma mark - MKMapViewDelegate
// 加载地图完成后调用,每次地图显示区域发生改变时都会加载地图并调用此方法
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {

    NSLog(@"地图加载完成");
}
/**
 *  方法说明:当用户的位置更新,就会调用(不断地监控用户的位置,调用频率特别高)
 *
 *  @param userLocation 表示地图上蓝色那颗大头针的数据(注意不是那个蓝色的视图,不复写该方法也可以显示蓝色的大头针)
 */
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {

    //用户位置的坐标
    CLLocationCoordinate2D center = userLocation.location.coordinate;
    NSLog(@"%f %f", center.latitude, center.longitude);
    // 可以定义用户位置的大头针属性
    userLocation.title = @"北京";
    userLocation.subtitle = @"天朝帝都";

    // 刷新用户位置时,地图显示的区域默认以用户位置为中心,但是默认显示的区域范围较小,我们可以在这里设置一下地图显示的区域
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 实践发现,在定位刷新结束后立即调用该方法是无法修改地图显示区域的范围的,所以我们延迟调用一下,哪怕仅仅是0.1秒
        [self setMapViewRegin];
    });

}
// 屏幕中显示的地图区域(中心点或是范围)发生变化时调用该方法
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {

    //附近的人,附近的商家,显示的地图区域改变时,显示的数据也需要改变
    NSLog(@"显示的地图范围将要发生变化");
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {

    NSLog(@"显示的地图范围已经发生变化");

    // 地图显示区域
    MKCoordinateRegion region = mapView.region;
    CLLocationCoordinate2D center = region.center;
    NSLog(@"中心点纬度:%f 经度:%f",center.latitude,center.longitude);
    MKCoordinateSpan span = region.span;
    NSLog(@"显示区域范围%f  %f",span.latitudeDelta,span.longitudeDelta);

}

#pragma mark - UIActions
- (IBAction)backUserLocation:(id)sender {

    [self showUserLocation];
//    [self setMapViewRegin];
}

#pragma mark - 私有方法
// 显示用户所在位置
- (void)showUserLocation {
    // 注意该方法应该在确定定位成功之后设置,否则无法获取用户的位置

    // 获取用户位置的坐标
    CLLocationCoordinate2D coordinate = self.mapView.userLocation.location.coordinate;
    [self.mapView setCenterCoordinate:coordinate animated:YES];

}

// 设置地图的显示区域
- (void)setMapViewRegin {
    // 注意该方法应该在确定定位成功之后设置,否则无法获取用户的位置

    // 获取用户位置作为地图显示区域中心
    CLLocationCoordinate2D coordinate = self.mapView.userLocation.location.coordinate;
    // 定义经纬度作为地图显示区域范围
    MKCoordinateSpan span = MKCoordinateSpanMake(1, 1);
    // 定义地图显示的区域
    MKCoordinateRegion regin = MKCoordinateRegionMake(coordinate, span);
    // 设置地图显示区域
    [self.mapView setRegion:regin animated:YES];
}

@end

注意:上面的代码有一个坑点,就是当我们刷新用户位置时,首次看到的地图显示区域是默认的,但是默认显示区域的范围是很小的,我们要想看到跟大的区域只能在代理方法中自行设置,但是我们看到,如果在位置刷新后立即设置的话,是没有任何的效果的。我们需要延迟调用设置显示区域的方法,哪怕仅仅是0.1秒。另外,模拟器默认的坐标为苹果总部,我们可以自定义模拟器所在的地理位置。
地图与定位(二)系统地图_第1张图片
另外,在上面的代码中,有几个结构体需要在这里额外的做一些说明:

  • MKCoordinateRegion 用来表示地图显示区域的一个结构体
    typedef struct {
    CLLocationCoordinate2D center;//区域的中心点
    MKCoordinateSpan span;//区域跨度
    } MKCoordinateRegion;

  • CLLocationCoordinate2D 用来表示地理坐标的一个结构体
    typedef struct {
    CLLocationDegrees latitude;// 坐标纬度
    CLLocationDegrees longitude;// 坐标经度
    } CLLocationCoordinate2D;

  • MKCoordinateSpan 用来表示地图显示区域跨度的一个结构体
    typedef struct {
    CLLocationDegrees latitudeDelta;//纬度跨度
    CLLocationDegrees longitudeDelta;//经度跨度
    } MKCoordinateSpan;


2. 添加地图标记

下面我们来看一下iOS中如何在地图中标记位置,运行上面的代码,我们可以在地图上看到一个蓝色的圆点,用来标记用户的位置。在iOS地图开发中经常会需要标记某个位置,需要使用地图标注,也就是大家俗称的“大头针”。

只要一个NSObject类实现MKAnnotation协议就可以作为一个大头针。我们在上面的代码中获取的用户位置数据都是封装在一个MKUserLocation实例对象中的,MKUserLocation就是一个实现了MKAnnotation协议的NSObject子类。但是MKUserLocation的属性大多都是只读的,所以我们不能直接使用MKUserLocation类来保存大头针数据。我们可以自己实现一个NSObject子类并实现MKAnnotation协议作为我们添加的大头针的数据模型。一般我们在自定的子类中重写协议中coordinate(标记位置)、title(标题)、subtitle(子标题)三个属性,然后在程序中创建大头针对象并调用addAnnotation:方法添加大头针即可;

示例代码:
自定义大头针数据模型:
Annotation.h 大头针数据模型

#import 
#import 

@interface Annotation : NSObject<MKAnnotation>

// 大头针标记的地理坐标
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
// 大头针标题
@property (nonatomic, copy) NSString *title;
// 大头针子标题
@property (nonatomic, copy) NSString *subtitle;

@end

根据大头针数据模型,填充地图标记数据,并添加大头针

#import "ViewController.h"
#import 
#import 

#import "Annotation.h"// 自定义大头针数据模型
@interface ViewController ()<CLLocationManagerDelegate,MKMapViewDelegate>

@property (nonatomic, strong)CLLocationManager *locationManager;
@property (nonatomic, strong)MKMapView *mapView;

@end

@implementation ViewController

- (CLLocationManager *)locationManager
{
    if (!_locationManager) {
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
    }
    return _locationManager;
}
- (MKMapView *)mapView {

    if (!_mapView) {
        _mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
        //设置mapView的属性
        self.mapView.mapType =  MKMapTypeStandard;//默认
        //设置用户跟踪模式
        self.mapView.userTrackingMode = MKUserTrackingModeFollow;
        _mapView.delegate = self;
    }
    return _mapView;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    // 添加地图
    [self.view addSubview:self.mapView];

    //开启定位服务
    //1.判断手机定位服务是否打开
    if (![CLLocationManager locationServicesEnabled]) {
        NSLog(@"手机定位服务没有打开");
        return;
    }
    //2.iOS8.0以上的用户需要授权
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
        if ([[[UIDevice currentDevice]  systemVersion] floatValue] >= 8.0) {
            //调用此方法之前必须在plist文件中添加NSLocationWhenInUseUsageDescription --string-- 后面跟的文字就是提示信息
            [self.locationManager requestWhenInUseAuthorization];
        }
    }

    //添加单击的手势,添加大头针
    [self.mapView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)]];

}

//将大头针添加到鼠标(手指)点击的地图位置
- (void)tapAction:(UITapGestureRecognizer *)tap {


    //1.获取用户手指触摸屏幕的坐标(x,y)
    CGPoint point = [tap locationInView:tap.view];
    NSLog(@"%@",NSStringFromCGPoint(point));

    //2.将屏幕上的坐标转换成经纬度(数学坐标转换成地理经纬度)
    //- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view;
    CLLocationCoordinate2D location = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
    NSLog(@"(%f,%f)",location.latitude,location.longitude);

    //MKUserLocation属性为只读的,不能作为自定义大头针的类存在
//    MKUserLocation *anno = [[MKUserLocation alloc] init];
//    anno.location = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];

    //3.子类化大头针模型,实现大头针协议
    //4.创建大头针
    Annotation *anno = [[Annotation alloc] init];
    anno.coordinate = location;//设置坐标
    anno.title = @"贝沃汇力";
    anno.subtitle = @"iOS 培训的黄埔军校";

    //4.添加
    [self.mapView addAnnotation:anno];
//    self.mapView addAnnotation:<#(nonnull id)#>

}

#pragma mapViewDelegate
/**
 *  用户位置发生改变的时候调用这个方法
 *
 *  @param mapView      地图
 *  @param userLocation 地图上蓝色的那颗大头针数据(注意不是那个蓝色的视图)不复写该方法也可以显示
 */
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
    userLocation.title = @"北京市";
    userLocation.subtitle = @"天朝帝都";
    NSLog(@"%f , %f",userLocation.location.coordinate.latitude,userLocation.location.coordinate.longitude);
    NSLog(@"%@",userLocation);

    //设置地图显示的区域
    //获取中心点位置
    CLLocationCoordinate2D center = userLocation.location.coordinate;
    //显示跨度
    MKCoordinateSpan span = MKCoordinateSpanMake(3, 3*(375/667));
    //region
    MKCoordinateRegion region = MKCoordinateRegionMake(center,  span);
    [mapView setRegion:region animated:YES];
}

@end

你可能感兴趣的:(地图和定位)