IOS_地图与定位

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IOS_地图与定位相关的知识,希望对你有一定的参考价值。

知识点介绍

一. 定位

  1. 实现一次定位 
  2. CLLocation对象介绍 
  3. 实现持续定位 
  4. 请求用户授权 

二. 地理编码

  1. 正地理编码 
  2. 反地理编码 

三. 地图的基本使用

  1. 显示用户位置 
  2. 设置地图显示类型 
  3. 根据用户位置显示对应的大头针信息 
  4. 设置以用户所在位置为中心点 
  5. 监听地图显示区域改变 
  6. ios9新特性-显示交通状况 / 显示比例 / 显示指南针 

四. 大头针的使用

  1. 添加大头针 
  2. 自定义大头针1, 更改颜色, 设置掉落效果 
  3. 自定义大头针2-更改大头针的图像 
  4. 自定义大头针的代码封装 

 

一. CoreLocation实现定位

在需要使用的时候, 导入CoreLocation头文件即可

1. 实现一次定位 (掌握)

  • 创建位置管理器: CLLocationManager
  • 设置代理
  • 请求用户授权: 配置plist文件 / 兼容iOS7的判断
  • 开始更新用户位置: startUpdatingLocation方法
  • 在位置管理器的didUpdateLocations代理方法中停止位置更新: stopUpdatingLocation
  • Xcode5之前, 使用其他的系统框架时, 必须要导入对应的框架才可以使用. Xcode5以后, 默认导入UIKit, Foundation, CoreGraphics三个框架. 也从Xcode5开始, 很多自带的框架可以不用导入, 系统会帮我们自动导入对应框架. 但是有些框架在使用时必须导入框架, 譬如Sqlite

//导入头文件
#import <CoreLocation/CoreLocation.h>
//注意, 模拟器需要手动设置位置.
//并且可能会有bug,定位不到, 需要切换模拟器.

//1. 创建位置管理器
self.locationManager = [CLLocationManager new];

//2. 请求用户授权(iOS8以后才有的. 如果是之前的系统, 系统会自动弹出授权框)
//判断系统的版本即可
if ([UIDevice currentDevice].systemVersion.doubleValue >= 8.0) {
    
    //请求用户授权--> 当用户在使用的时候授权, 还需要增加info.plist的一个key
    [self.locationManager requestWhenInUseAuthorization];
}

//3. 开启定位
[self.locationManager startUpdatingLocation];


//4. 设置代理--> 获取定位的信息
self.locationManager.delegate = self;

//5. 实现代理方法, 当获取到位置之后, 停止定位
//当获取到位置之后, 会调用此方法

//用户更新位置会调用此方法,    此方法频繁调用, 会非常耗电
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
    NSLog(@"location: %@", locations);
    [self.locationManager stopUpdatingLocation];
}

 

2. CLLocation对象介绍 (理解)

  • CLLocation介绍
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
    //CLLocationCoordinate2D coordinate 2D坐标/经纬度 是一个结构体, 包含了经纬度2个值
    //CLLocationDegrees latitude    (纬度) 180
    //CLLocationDegrees longitude   (经度) 360
    
    //CLLocation 位置对象, 里面包含的属性很多. 经纬度coordinate, 海拔altitude, 水平精准度horizontalAccuracy,  垂直精准度verticalAccuracy, 方向course
    
    
    CLLocation *location = locations.firstObject;
    NSLog(@"latitude: %f, longitude: %f ", location.coordinate.latitude, location.coordinate.longitude);
    //停止定位
    //[self.locationManager stopUpdatingLocation];
}
  • 比较两个位置的距离: 调用CLLocation的对象方法 distanceFromLocation

    //比较2个位置之间的距离(北京/西安) --> 练习如何创建CLLocation, 以及比较位置
    
    CLLocation *location1 = [[CLLocation alloc] initWithLatitude:40 longitude:116];
    CLLocation *location2 = [[CLLocation alloc] initWithLatitude:34.26 longitude:108.95];
    
    //单位是米, 比较的是两点之前直线距离 --> 此方法不需要记, 这个步骤应该是服务器帮你完成的
    CLLocationDistance distance = [location1 distanceFromLocation:location2];
    NSLog(@"distance: %f", distance / 1000);

3. 实现持续定位 (了解)

  • 禁用stopUpdatingLocation方法
  • 设置位置筛选器: distanceFilter
  • 设置精准度: desiredAccuracy
//1. 位置筛选器 --> 当位置发生了一定的改变之后, 再调用代理方法, .通过降低代理方法调用, 以此节省电量
self.locationManager.distanceFilter = 10;

//6. 期望精准度(减少与卫星之间的计算)
/*
 CLLocationAccuracy kCLLocationAccuracyBestForNavigation      最好的为导航用的
 CLLocationAccuracy kCLLocationAccuracyBest;                  最好的
 CLLocationAccuracy kCLLocationAccuracyNearestTenMeters;      十米
 CLLocationAccuracy kCLLocationAccuracyHundredMeters;         百米
 CLLocationAccuracy kCLLocationAccuracyKilometer;             千米
 CLLocationAccuracy kCLLocationAccuracyThreeKilometers;       三千米
 **/

//定位越精准, 越耗电, 通过降低精准度, 来节省电量 --> 主要是降低跟卫星之间的计算
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;

4. 请求用户授权 (理解)

  • 写代码请求用户授权: 用户使用期间授权requestWhenInUseAuthorization / 一直授权requestAlwaysAuthorization
  • 配置对应的 plist 列表(跳进方法的头文件, 在注释中拷贝Key): NSLocationWhenInUseUsageDescription 和 NSLocationAlwaysUsageDescription
//2. 请求用户授权(iOS8以后才有的. 如果是之前的系统, 系统会自动弹出授权框)
    //判断系统的版本即可
//    if ([UIDevice currentDevice].systemVersion.doubleValue >= 8.0) {}
    
    //2.1 判断有两种方式: 1. 判断系统版本  2. 判断能够响应方法
    //2.2 开发中, 只需要写一个授权方法即可. 大部分都是使用期间, 不需要写2个.
    //2.3 info.plistde Value可以不写, Key必须写. Key必须和方法对应
    
    
    
    if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
        //请求用户授权
        
        //当用户在使用的时候授权, 还需要增加info.plist的一个key
        //前台运行的时候 --> 当能看见程序在主界面运行
        [self.locationManager requestWhenInUseAuthorization];
        
        //后台运行的时候 --> 进入后台 / 锁屏
        //[self.locationManager requestAlwaysAuthorization];
    }

 

  //2. 请求用户授权(iOS8以后才有的. 如果是之前的系统, 系统会自动弹出授权框)
    //判断系统的版本即可
    if ([UIDevice currentDevice].systemVersion.doubleValue >= 8.0) {
        
        //请求用户授权--> 当用户在使用的时候授权, 还需要增加info.plist的一个key
        [self.locationManager requestWhenInUseAuthorization];
        //一直授权
//        [self.locationManager requestAlwaysAuthorization];

注意事项

  • iOS8开始, 定位用户所在位置, 必须使用CLLocationManager授权: 1. 写代码请求用户授权 2. 配置对应的 plist 列表(info.plist)
  • 大部分应用程序只需要使用: 用户使用期间授权即可. 没必要实现2种授权方法
  • plist 键值, 键对应的值可以不写. 最好还是写上, 填写为什么使用定位(提高用户打开的定位概率)

二. 地理编码和反地理编码

在需要使用的时候, 导入MapKit头文件即可

1. 地理编码 (理解)

正向地理编码服务实现了将中文地址或地名描述转换为地球表面上相应位置的功能

  • 创建地理编码对象: CLGeocoder
  • 调用方法进行地理编码: geocodeAddressString
  • 在block中获取地标对象: CLPlacemark
- (IBAction)geocoderClick:(id)sender {
    
    //地理编码: 将位置转成经纬度
    //1. 创建地理编码对象
    CLGeocoder *geocoder = [CLGeocoder new];
    
    //2. 调用地理编码方法
    [geocoder geocodeAddressString:self.addressTF.text completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
        
        //3. 根据地标对象, 获取对应的值
        
        //3.1 防错处理
        if (error || placemarks.count == 0) {
            NSLog(@"解析错误或者未解析到地址");
            return ;
        }
        
        //3.2 根据地标对象, 获取对应的值
        //CLPlacemark 地标对象
        
        //正向地理编码, 很有可能一个地名搜出多个位置, 实际开发, 应该给用户一个列表选择
        for (CLPlacemark *pm in placemarks) {
            
            NSLog(@"location: %f, %f", pm.location.coordinate.latitude, pm.location.coordinate.longitude);
            
            NSLog(@"name: %@", pm.name);
            NSLog(@"city: %@", pm.locality);
        }
   
    }];
    
}

2. 反地理编码 (掌握)

反向地理编码服务实现了将地球表面的地址坐标转换为标准地址的过程,反向地理编码提供了坐标定位引擎,帮助用户通过地面某个地物的坐标值来反向查询得到该地物所在的行政区划、所处街道、以及最匹配的标准地址信息。

  • 创建地理编码对象: CLGeocoder
  • 创建CLLocation对象
  • 调用方法进行反地理编码: reverseGeocodeLocation
  • 在block中获取地标对象: CLPlacemark
- (IBAction)reverseGeocoderClick:(id)sender {
    //1. 创建地理编码对象
    CLGeocoder *geocoder = [CLGeocoder new];
    
    //2. 调用反地理编码方法 --> 需要CLLocation对象
    //2.1 创建CLLocation
    CLLocation *location = [[CLLocation alloc] initWithLatitude:[self.latitudeTF.text doubleValue] longitude:[self.longitudeTF.text doubleValue]];
    
    [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
        
        //3. 根据地标对象, 获取对应的值
        //3.1 防错处理
        if (error || placemarks.count == 0) {
            NSLog(@"解析错误或者未解析到地址");
            return ;
        }
        
        //3.2 根据地标对象, 获取对应的值
        //反向地理编码, 只对应一个位置. 不需要循环获取
        CLPlacemark *pm = placemarks.firstObject;
        
        //40.058967, 116.335810
        NSLog(@"name: %@", pm.name);
        NSLog(@"city: %@", pm.locality);
    }];
}

三. 地图的基本使用

1. 设置地图显示类型 (掌握)

  • mapType
    /**
     MKMapTypeStandard = 0,    标准
     MKMapTypeSatellite,       卫星
     MKMapTypeHybrid,          混合
     MKMapTypeSatelliteFlyover NS_ENUM_AVAILABLE(10_11, 9_0), iOS9新出的, 中国区无效
     MKMapTypeHybridFlyover NS_ENUM_AVAILABLE(10_11, 9_0),
     */
    - (IBAction)mapTypeChange:(UISegmentedControl *)sender {
        switch (sender.selectedSegmentIndex) {
            case 0:
                self.mapView.mapType = 0;
                break;
                
            case 1:
                self.mapView.mapType = MKMapTypeSatellite;
                break;
                
            case 2:
                self.mapView.mapType = MKMapTypeHybrid;
                break;
                
            case 3:
                self.mapView.mapType = MKMapTypeSatelliteFlyover;
                break;
                
            case 4:
                self.mapView.mapType = MKMapTypeHybridFlyover;
                break;
            default:
                break;
        }
    }

    2. 显示用户位置 (掌握)

    • 请求授权 / 设置跟踪模式userTrackingMode
    //2. 用户授权 iOS8以后必须写
    //别忘记增加info.plist  key
    [self.locationManager requestWhenInUseAuthorization];
    
    //3. 定位 --> 除了locationManager可以定位之外, MKMapView也可以定位
    
    // 显示用户位置: 只能显示用户位置, 不会发生跟踪行为
    //self.mapView.showsUserLocation = YES;
    
    // 用户跟踪模式
    /**
     MKUserTrackingModeNone = 0,            不跟踪
     MKUserTrackingModeFollow,              跟踪
     MKUserTrackingModeFollowWithHeading    跟踪并显示头部方向
     */
    self.mapView.userTrackingMode = MKUserTrackingModeFollow;

3. 根据用户位置显示对应的大头针信息 (掌握)

  • didUpdateUserLocation代理方法

         MKUserLocation : 用户位置的大头针模型, 改模型就可以改视图

         - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{}

//当更新用户位置之后会调用的方法
//MKUserLocation : 用户位置的大头针模型
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
    //反地理编码 --> 获取经纬度 --> 转换成地址/城市
    
    //1. 创建地理编码对象
    CLGeocoder *geocoder = [CLGeocoder new];
    
    //2. 调用反地理编码方法 --> 需要CLLocation对象
    [geocoder reverseGeocodeLocation:userLocation.location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
        //3. 根据地标对象, 获取对应的值
        //3.1 防错处理
        if (error || placemarks.count == 0) {
            NSLog(@"解析错误或者未解析到地址");
            return ;
        }
        
        //3.2 根据地标对象, 获取对应的值
        //反向地理编码, 只对应一个位置. 不需要循环获取
        CLPlacemark *pm = placemarks.firstObject;
        //应该先判断是否有城市数据,如果没有就显示行政区域
        if(pm.locality){
        userLocation.title = pm.locality;
        }else{
            userLocation.title = pm.administrativeArea;
        }
        //设置子标题
         userLocation.subtitle = pm.name;
    }];
    
}

4. 设置以用户所在位置(经纬度)为中心点 (掌握)

  • centerCoordinate:只能返回用户中心点, 不会改变地图的显示跨度
  • region: 既可以返回用户中心点, 也可以设置显示的跨度

5. 获取地图的显示大小和中心经纬度 (了解)

  • mapView.region.center / mapView.region.span
//1. 设置中心点经纬度
//只要定位到了用户位置, 就会在mapView中有值
//[self.mapView setCenterCoordinate:self.mapView.userLocation.coordinate animated:YES];

//2. 设置范围 可以改变地图显示大小
//region是一个结构体, 里面又包含了2个结构体, 共4个double值组成.

//2.1 中心点经纬度
CLLocationCoordinate2D centerCoordinate = self.mapView.userLocation.coordinate;

//2.2 范围   1°=111KM 设置显示的跨度
MKCoordinateSpan span = MKCoordinateSpanMake(0.01, 0.01);

//2.3 创建Region
[self.mapView setRegion:MKCoordinateRegionMake(centerCoordinate, span); animated:YES];

6. iOS9新特性 (了解)

  • 显示交通状况: showsTraffic
  • 显示比例: showsScale
  • 显示指南针(默认就有): showsCompass
       /**
         iOS9新特性-显示交通状况 / 显示比例 / 显示指南针
         */
        
        //5. 显示交通状况
        self.mapView.showsTraffic = YES;
        
        //6. 显示比例尺
        self.mapView.showsScale = YES;
        
        //7. 显示指南针(默认就有, 只不过可以关闭)
        self.mapView.showsCompass = YES;

     

四. 添加大头针

1. 添加大头针(掌握)

  • 根据给定的坐标添加: 自定义大头针模型, 然后添加
#import <MapKit/MapKit.h>

//点击屏幕添加大头针
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //最核心的, 获取点击的点, 然后转换成经纬度
    
    //1. 获取点击的点
    CGPoint point = [[touches anyObject] locationInView:self.mapView];
    
    //2. 然后转换成经纬度 --> MapView需要转换
    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
    
    //3. 创建模型
    MyAnnotationModel *annoModel = [MyAnnotationModel new];
    //模型的经纬度从 点击的点中获取
    annoModel.coordinate = coordinate;
    //标题/子标题 --> 反地理编码 传入经纬度可以获取地名和城市
    
    annoModel.title = @"广东";
    annoModel.subtitle = @"广东一个令人向往的城市";
    
    //4. 添加到地图上
    [self.mapView addAnnotation:annoModel];
}

  • 自定义大头针1, 更改颜色, 设置掉落效果 (理解)
//此方法可以自定义大头针View
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
    //如果return nil, 就代表系统会自动处理大头针View的显示
    
    //为了保证显示用户大头针的视图, 是系统的, 这里需要增加判断
    //MKUserLocation: 系统类
    //MyAnnotationModel: 自己的类
    
    //1. 判断系统类, 如果是系统就返回nil
    if ([annotation isKindOfClass:[MKUserLocation class]]) {
        return nil;
    }
    
    
    //2. 创建自定义的view
    //此处跟创建cell的过程很像
    
    static NSString *ID = @"annoView";
    
    //MKAnnotationView: 如果自定义了, 那么这个类的image属性就为kong
    //MKPinAnnotationView: 是上面的子类, 默认image有图像的, 而且能够设置颜色及动画掉落
    
    MKPinAnnotationView *annoView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:ID];
    
    if (annoView == nil) {
        annoView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ID];
        
        //1. 如果自定义了大头针视图, 那么交互的一个属性默认为NO, 需要打开
        //Callout: 回调
        annoView.canShowCallout = YES;
        
        //2. 设置颜色
        //pinColor : iOS9以前的属性, 只能设置3种颜色
        annoView.pinTintColor = [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0];
        
        //3. 设置动画掉落
        annoView.animatesDrop = YES;
        
    }
    return annoView;
}

  • 自定义大头针2-更改大头针的图像 (掌握)
#pragma mark 地图的代理方法
//此方法可以自定义大头针View
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(MyAnnotationModel *)annotation
{
    //1. 判断系统类, 如果是系统就返回nil
    if ([annotation isKindOfClass:[MKUserLocation class]]) {
        return nil;
    }

    //2. 创建自定义的view
    static NSString *ID = @"annoView";
    
    //MKAnnotationView: 如果自定义了, 那么这个类的image属性就为kong
    MKAnnotationView *annoView = [mapView dequeueReusableAnnotationViewWithIdentifier:ID];
    
    if (annoView == nil) {
        annoView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ID];
        
        //1. 如果自定义了大头针视图, 那么交互的一个属性默认为NO, 需要打开
        //Callout: 回调
        annoView.canShowCallout = YES;
    }
    
    //2. 设置图像
    //图像, 需要和标题/子标题一样, 最好从模型中获取
    annoView.image = [UIImage imageNamed:annotation.icon];
    
    return annoView;
}

#pragma mark 当大头针view没有出现之前会调用的方法
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray<MKAnnotationView *> *)views
{
    //此方法在出现之前会被调用
    
    for (MKAnnotationView *subView in views) {
        
        //0. 取消系统大头针的动画掉落
        //MKModernUserLocationView: 是显示用户位置的类
        //MKAnnotationView: 自定义的类
        
        if ([subView isKindOfClass:NSClassFromString(@"MKModernUserLocationView")]) {

            //跳过本次循环.
            continue;
        }

        //1. 记录原始位置
        CGRect endFreme = subView.frame;
        
        //2. 改Y为值0
        subView.frame = CGRectMake(endFreme.origin.x, 0, endFreme.size.width, endFreme.size.height);
        
        //3. 动画回归
        [UIView animateWithDuration:5 animations:^{
            subView.frame = endFreme;
        }];
    }
}


//点击屏幕添加大头针
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    MyAnnotationModel *annoModel1 = [MyAnnotationModel new];
    annoModel1.coordinate = CLLocationCoordinate2DMake(36, 114);
    annoModel1.title = @"系数";
    annoModel1.subtitle = @"广啥地方人向往的城市";
    annoModel1.icon = @"自拍照";
    [self.mapView addAnnotation:annoModel1];
    
    MyAnnotationModel *annoModel = [MyAnnotationModel new];
    annoModel.coordinate = CLLocationCoordinate2DMake(23, 123);
    annoModel.title = @"广东";
    annoModel.subtitle = @"广东一个令人向往的城市";
    annoModel.icon = @"苍老师";
    [self.mapView addAnnotation:annoModel];
}

 

  • 自定义大头针的代码封装 (理解)
#import "MyAnnotationView.h"
#import "MyAnnotationModel.h"

@implementation MyAnnotationView


+ (instancetype)myAnnotiaonViewWithMapView:(MKMapView *)mapView
{
    //2. 创建自定义的view
    static NSString *ID = @"annoView";
    
    //MKAnnotationView: 如果自定义了, 那么这个类的image属性就为kong
    
    MyAnnotationView *annoView = (MyAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:ID];
    
    if (annoView == nil) {
        //系统会自动设置模型, 因此不需要传递模型数据
        annoView = [[MyAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:ID];
        
        //1. 如果自定义了大头针视图, 那么交互的一个属性默认为NO, 需要打开
        //Callout: 回调
        annoView.canShowCallout = YES;
        
        //2. 添加左边附属视图
        annoView.leftCalloutAccessoryView = [UISwitch new];
        
        //3. 添加右边附属视图
        annoView.rightCalloutAccessoryView = [UISwitch new];
        
        //4. 添加详情附属视图
        annoView.detailCalloutAccessoryView = [UISwitch new];
    }
    return annoView;
}

//属性的set方法, 系统自己会设置. 重写了就会把父类自己写的方法覆盖了
- (void)setAnnotation:(MyAnnotationModel *)annotation
{
    [super setAnnotation:annotation];
    
    //设置图像
    //图像, 需要和标题/子标题一样, 最好从模型中获取
    self.image = [UIImage imageNamed:annotation.icon];
}

 

以上是关于IOS_地图与定位的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发之地图与定位

iOS_SN_地图的使用

iOS开发 定位服务与地图

ios之定位与地图

iOS开发系列--地图与定位

如何自定义百度地图的定位图标 ios