• 【iOS干货】☞ 定位


    一、简介

      定位是一个很常用的功能,如一些地图软件打开之后 如果用户允许软件定位的话,那么打开软件后就会自动锁定到当前位置,如果用户手机移动那么当前位置也会跟随着变化。要实现这个功能需要使用Core Loaction中CLLocationManager类。

    1. 定位的实际应用场景:

    • 导航:去任意陌生的地方
    • 周边:找餐馆、找酒店、找银行、找电影院等

    2. 涉及技术:

    • Core Location 框架用于地理定位。(单纯的定位,不需要显示地图)

    常用类:以CL前缀开头

    CLLocation:(结构体类型)经纬度

    CLLocationManager:  定位管理类,位置管理器,全局唯一存在,做定位用。 

    CLLocationManagerDelegate:  监听用户是否愿意定位(iOS8后要问),监听用户的位置(经纬度)

    • Map Kit 框架用于地图展示。(和地图结合的定位,如:百度地图、高德地图等)

    常用类:以MK前缀开头

    MKMapView: 显示地图视图

    MKMapViewDelegate: 地图视图的协议(定位;地图视图移动;定位用户的位置)

    3. 专业术语:

    • LBS:Location Based Service,基于位置的服务。(如,打车:基于位置提供了叫车服务。)
    • SoLoMo:Social Local Mobile(索罗门),社交本地移动。(如,陌陌、微信、QQ)

    4. iOS定位的方式:(按定位准确性排名)

    • GPS(Global Positioning System,全球卫星定位系统)定位
    • 移动基站/蜂窝/流量
    • wifi定位

    二、用户隐私的保护

    1. 权限设置说明

      从iOS 6开始,苹果在保护用户隐私方面做了很大的加强,以下操作都必须经过用户批准授权

        (1)要想获得用户的位置

        (2)想访问用户的通讯录、日历、相机、相册等

        当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权

      

      注意:一旦用户选择了“Don’t Allow”,意味着你的应用以后就无法使用定位功能,且当用户第一次选择了之后,以后就再也不会提醒进行设置。

      因此在程序中应该进行判断,如果发现自己的定位服务没有打开,那么应该提醒用户打开定位服务功能。

      CLLocationManager有个类方法可以判断当前应用的定位功能是否可用+ (BOOL)locationServicesEnabled;

      常用的方法:截图告诉用户,应该怎么打开授权

    2. 请求用户授权

      配置征求用户是否同意授权的弹出框:(在Info.plist里配置,添加相应的key 才能弹出授权框)

    • 始终授权:

        

       

    • 使用应用期间授权:

      

      

    • 修改plist配置,添加Key:

       

      

    3. 自定义模拟器的位置

      自定义一个位置模拟位置,写哪儿模拟器就认为你在哪儿(定位就定在这儿)

       

    二、CoreLocation框架的使用

    1. 导入框架:CoreLocation.framework

    2. 导入头文件:#import <CoreLocation/CoreLocation.h>

    3. CLLocationManager的常用操作:

    • 开始用户定位:- (void)startUpdatingLocation;
    • 停止用户定位:- (void)stopUpdatingLocation;
    • 当调用了startUpdatingLocation方法后,就开始不断地定位用户的位置,中途会频繁地调用代理的下面方法:

        #pragma mark - CLLocationManagerDelegate 实现协议中的方法

        - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;

     4. 定位用户的位置:

    #import "ViewController.h"
    #import <CoreLocation/CoreLocation.h> // 导入头文件    
    @interface ViewController () <CLLocationManagerDelegate>
    // 声明一个属性帮助我们来定位,干什么都要问他
    @property (nonatomic, strong) CLLocationManager *manager;
    @end
    
    @implementation ViewController
    #pragma mark ---懒加载
    -(CLLocationManager *)manager {
        if (_manager==nil) {
            // 1.创建位置管理器(定位用户的位置)
            _manager=[[CLLocationManager alloc]init];
            // 2.设置代理(设置谁来监听用户的位置)
            _manager.delegate=self;
        }
        return _manager;
    }
    - (void)viewDidLoad {
        [super viewDidLoad];
        // 请求用户授权
        // iOS8之后才开始征求用户同意,iOS8之前不用征求同意直接定位
        if ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) {
      // 注意:iOS8之后要配置info.plist文件:添加key(NSLocationAlwaysUsageDescription / NSLocationWhenInUseUsageDescription),才能弹出授权框
         // 两种授权:始终(永久)授权、使用应用期间授权。
         // 注意:如果同时写了下面两种授权,程序打开的时候会出现两次授权提示。大多数情况下,我们根据程序的需求写一种授权方式就可以了。        
    //1) 永久授权。无论当前程序在前台或后台都授权/都定位 (key:NSLocationAlwaysUsageDescription)         //[self.manager requestAlwaysAuthorization]; //请求总是授权         //2) 当用户正在使用的时候授权。只有程序在前台运行的时候才会授权(征求用户是否愿意只在前台定位) (key:NSLocationWhenInUseUsageDescription)         [self.manager requestWhenInUseAuthorization]; //请求在使用时的授权(在前台),大多数APP使用的是这种授权              } else {         // 调用开始定位方法。直接定位(不需要征求用户同意) 定位里面干什么都要问manager         [self.manager startUpdatingLocation];//开始定位     } } #pragma mark - CLLocationManagerDelegate 实现协议中的方法 //1.查看用户是否同意(这个方法监听用户有没有点允许/不允许),用户同意了就调用第二个方法 /**  *  @param status  用户授权的状态 (用户是否同意)  *  常用的两个状态:  *   1) kCLAuthorizationStatusDenied:用户不同意定位  *      用户不允许自动定位时,可以手动选择城市定位(如:墨迹天气,用户手动选择一个城市,把城市的天气推送给你)  *   2) kCLAuthorizationStatusAuthorizedWhenInUse:用户允许在使用期间(前台)定位  */ -(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {     //判断两种常用的授权状态     switch (status) {         case kCLAuthorizationStatusAuthorizedWhenInUse: //用户允许在使用期间(前台)定位
         // 实现持续定位,通常要做一些优化(目的是节省电量和流量)。
          // 1. 设置距离筛选器,当用户位置发生一定改变之后再调用代理方法2(避免调用太频繁)。
           // 当用户位置发生超过10米的变化,再重新开始定位(即调用代理方法2)
           
    self.manger.distanceFilter = 10;             // 2. 设置定位的精准度(常量值:kXXXBest最好的、十米范围内、百米范围内、千米范围内、三千米范围内)             // 我们可以降低定位的精准度,实际上降低了与卫星之间的计算,以此节省电量和流量。精确度越高越费电量/流量,一般选十米/百米范围内为宜。             self.manager.desiredAccuracy = kCLLocationAccuracyBest;//定位的精确度             [self.manager startUpdatingLocation]; //开始定位操作             break;         case kCLAuthorizationStatusDenied: //用户不允许定位(第二种方案)             NSLog(@"用户不允许定位!");             break;         default:             break;     } } //2.已经定位到用户的位置会调用这个方法 /**  *  当完成位置更新的时候调用。当定位到用户的位置时,就会调用(调用的频率比较频繁)
    * 当调用了startUpdatingLocation方法后,就开始不断地定位用户的位置,中途会频繁地调用代理的下面方法  *  @param locations 用户的位置(数组类型,最后一项是用户最新的位置;数组里至少有一项)  
    */ -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {     CLLocation *location=[locations lastObject];//获取用户最新的位置     NSLog(@"纬度:%f, 经度:%f", location.coordinate.latitude, location.coordinate.longitude); //打印获取用户位置的经纬度     // 停止用户定位/停止更新位置(不停止,就会一直不停的定位,即持续定位;停止后该代理方法就不会再频繁调用,即一次定位)     [self.manager stopUpdatingLocation];     // Xcode7以下只定位一次(打印经纬度一次),Xcode7以上会定位三次(打印经纬度三次) } @end

     注意:如果发现自己的定位服务没有打开,那么应该提醒用户打开定位服务功能。定位服务是比较耗电的,如果是做定位服务(没必要实时更新的话),那么定位了用户位置后,应该停止更新位置。

    5. 计算两点之间的直线距离

    // 比较两个位置之间的距离(如,北京与西安的距离)
    CLLocation *location1 = [[CLLocation alloc]initWithLatitude:40 longitude:116];
    CLLocation *location2 = [[CLLocation alloc]initWithLatitude:34.27 longitude:108.93];
    // 比较直线距离
    CLLocationDistance distance = [location1 distanceFromLocation:location2];
    NSLog(@"北京与西安的直线距离为:%f千米", distance / 1000);

    三、地理编码/反地理编码

    1. 简单说明:

        Geography(地理) + code(编码),CLGeocoder:地理编码器,其中Geo是地理英文单词Geography的简写。

    • 地理编码将地名 转换成 经纬度的过程。给定一个地址名(城市/街道/省名字) —> 返回地址名所在的位置(经纬度)
    • 反地理编码 (使用频率高):将经纬度 转换成 地名的过程。给定一个经纬度 —> 返回该经纬度的详细信息(国家/省/城市/街道/店铺)

    2. CLGeocoder 的使用:

        使用 CLGeocoder 可以完成“地理编码”和“反地理编码”

        

    3. CLPlacemark 类常用属性:

    @interface CLPlacemark : NSObject  //地标类
    @property (nonatomic, readonly, copy) CLLocation *location; //地理位置,可以获取经纬度
    @property (nonatomic, readonly, copy) NSDictionary *addressDictionary; //详细的地址信息
                
    @property (nonatomic, readonly) CLRegion *region; //区域
    @property (nonatomic, readonly) NSString *name; //地址名称
    @property (nonatomic, readonly) NSString *locality; //城市
                
    @end

     4. 完整代码:

    // 1. 创建一个CLGeocoder对象
    CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    // 2. 开始地理编码
    /**
     *  说明:调用下面的方法开始编码,不管编码是成功还是失败都会调用block中的方法
     *  给一个地名,返回一个block回调参数
     *  @param placemarks 地标数组,主要的是CLLocation / 城市属性
     */
    [geocoder geocodeAddressString:@"下沙" completionHandler:^(NSArray *placemarks, NSError *error) {
        //1)如果有错误信息,或者是数组中获取的地名元素数量为0,那么说明没有找到
        if (placemarks.count == 0 || error) {
            NSLog(@"你输入的地址没找到,可能在火星上");
        } else {
            //2)编码成功,找到了具体的位置信息
            /**
             *  遍历地表数组:
             *  这里数组中有一个/多个相关的位置信息对象(给一个名称,可能对应多个位置信息)
             */
            for (CLPlacemark *placemark in placemarks) {
             // 打印查看找到的所有的位置信息
                NSLog(@"详细地址名称:%@", placemark.name);
                NSLog(@"经纬度坐标:%.4f, %.4f" , placemark.location.coordinate.longitude, placemark.location.coordinate.latitude);
            }
            // 取得第一个地标,地标中存储了详细的地址信息,注意:一个地名可能搜索出多个地址
            CLPlacemark *placemark = [placemarks firstObject];
            //1>详细地址名称
            NSLog(@"详细地址名称:%@", placemark.name);
            //2>经纬度
            NSLog(@"经纬度坐标:%.4f, %.4f" , placemark.location.coordinate.longitude, placemark.location.coordinate.latitude);
        }
    }];
    
    // 1. 设置经纬度 CLLocation *location = [[CLLocation alloc]initWithLatitude:40 longitude:116]; // 2.开始反地理编码 [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) { // 1)如果有错误信息,或者是数组中获取的地名元素数量为0,那么说明没有找到 if (placemarks.count == 0 || error) { NSLog(@"你输入的地址没找到,可能在火星上"); } else { //2)编码成功 //这里不用for循环遍历,因为数组中只有唯一的一个对象(经纬度一定,地名也一定),直接取出即可 CLPlacemark *placemark = [placemarks firstObject]; self.reverseDetailAddressLabel.text = placemark.name; NSLog(@"详细地址名称:%@", placemark.addressDictionary[@"FormattedAddressLines"]); } }];

    四、MapKit框架的使用(和地图结合的定位)

    1. 导入框架:MapKit.framework

    2. 导入头文件:#import <MapKit/MapKit.h>

    3. 经纬度:

     

    4. MapView的使用:

      MapKit中有一个比较重要的UI控件:MKMapView,专门用于地图显示。

      

    • 实现和地图结合的定位:
    #import "ViewController.h"
    #import <MapKit/MapKit.h>
    
    @interface ViewController () <MKMapViewDelegate>
    @property (weak, nonatomic) IBOutlet MKMapView *mapView;
    @property (nonatomic, strong) CLLocationManager *manager;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // 初始化manager
        self.manager = [[CLLocationManager alloc]init];
        // 请求授权:征求用户同意(假定同意,在targets-->Info/Info.plist中添加key)
        if ([self.manager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
            [self.manager requestWhenInUseAuthorization];
        }
        //设置地图视图的代理
        self.mapView.delegate = self;
      // 设置地图的属性
        self.mapView.rotateEnabled = NO;//设置不允许旋转
        self.mapView.mapType = MKMapTypeHybrid;//地图的显示类型(混合类型,默认是Standard类型)
        // 设定地图属性(开始定位/已经把用户的位置显示在地图上)
        // 用户的跟踪模式,地图视图的跟踪模式是跟随着用户的位置而变化,当前地图一定会显示用户的位置
        self.mapView.userTrackingMode=MKUserTrackingModeFollow;
        
    }
    #pragma mark - MKMapViewDelegate 协议
    // 完成用户位置更新的时候调用,已经定位到用户的位置调用这个方法
    -(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
      // 已经定位到用户的位置
        NSLog(@"location:%f, %f",userLocation.location.coordinate.latitude, userLocation.location.coordinate.longitude);
      userLocation.title = @"用户位置-标题";
      userLocation.subtitle = @"用户位置-子标题";
      // 这里可以通过反地理编码把经纬度转成地名,用户点击时就可以显示用户位置的提示。 }
    @end

     

    5. 回到用户的位置(中心点回到原来的位置、跨度和原来保持一致)

      

      

  • 相关阅读:
    Contoso 大学 1 为 ASP.NET MVC 应用程序创建 EF 数据模型
    NuGet 入门
    关于Oracle表及字段的注释 转
    javascript + xmlhttp 调用webservice 吃力不讨好
    转 javascript小技巧
    oracle电子书下载站
    JavaScript中引号的嵌套
    数据结构中的各种排序方法JS实现
    TSQL查询进阶流程控制语句
    TSQL查询进阶数据集之间的运算
  • 原文地址:https://www.cnblogs.com/bossren/p/6912041.html
Copyright © 2020-2023  润新知