Objective-C的CLLocation学习笔记

CLLocation - 位置对象

CLLocation对象包含设备的地理位置和高度,以及指示这些测量精度和收集时间的值。在iOS操作系统中,位置对象还包含航向信息,即设备移动的速度和航向

通常不需要自己创建CLLocation对象。从CLLocationManager对象请求位置更新后,系统使用板载传感器收集位置数据,并将数据报告给应用程序。一些服务还会返回之前收集的位置数据,可以将这些数据作为上下文来改进服务。始终可以从CLLocationManager对象的UIApplicationLaunchOptionsLocationKey属性中检索最近收集的位置。如果要缓存自定义位置数据或计算两个地理坐标之间的距离,可以自己创建location对象。需要按原样使用CLLocation对象,不要子类化它们

常用类型定义
typedef double CLLocationDegrees;

类型定义用于表示WGS 84参考坐标系下纬度或经度坐标(以度为单位)的类型。单位度数可以是正(北和东)或负(南和西)。

例如 :

CLLocationDegrees latitude = 45.148327;
CLLocationDegrees longitude = 124.818265;
typedef double CLLocationAccuracy;

类型定义用于表示以米为单位的位置精度级别的类型。单位为米的值越低,位置的物理精度就越高。负精度值表示位置无效。

typedef double CLLocationSpeed;

类型定义设备移动的速度(以米每秒为单位的类型)。

typedef double CLLocationDirection;

类型定义相对于正北以度数测量的方位角。方向值从正北开始以度为单位测量,并围绕指南针顺时针继续。因此,北是0度,东是90度,南是180度,以此类推。负值表示方向无效。

struct CLLocationCoordinate2D {
    CLLocationDegrees latitude;
    CLLocationDegrees longitude;
};
typedef struct CLLocationCoordinate2D CLLocationCoordinate2D;

结构体描述使用WGS 84参考坐标系指定的与位置相关联的纬度和经度

CL_EXTERN
CLLocationCoordinate2D CLLocationCoordinate2DMake(CLLocationDegrees latitude, CLLocationDegrees longitude) API_AVAILABLE(ios(4.0), macos(10.7));

函数描述将纬度和经度值格式化为坐标数据结构格式

参数 :

latitude : 新坐标的纬度。

longitude : 新坐标的经度。

返回值 : 包含纬度和经度值的坐标结构。

例如:

CLLocationDegrees latitude = 45.148327;
CLLocationDegrees longitude = 124.818265;
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);

CLLocationManager - 位置管理器

CLLocationManager,用于启动和停止向应用程序传递位置相关事件的对象。可以使用该类的实例来配置、启动和停止Core Location(核心位置)服务。位置管理器对象支持以下与位置相关的活动:

  • 以可配置的精确度跟踪用户当前位置的大小变化。
  • 报告来自船上罗盘的航向变化。(iOS)
  • 监视感兴趣的不同区域,并在用户进入或离开这些区域时生成位置事件。
  • 当应用程序在后台时,延迟位置更新的交付。(iOS)
  • 向附近的信标报告范围。

准备使用定位服务时,请执行以下步骤

  • 1.查看应用程序是否被授权使用位置服务,如果您的应用程序的授权状态尚未确定,则请求权限。
  • 2.检查是否有适当的位置服务可供使用。
  • 3.创建一个CLLocationManager类的实例,并在应用程序的某个地方存储对它的强引用。在所有涉及到该对象的任务完成之前,需要保持对CLLocationManager对象的强引用。由于大多数位置管理器任务是异步运行的,因此将位置管理器存储在局部变量中是不够的。
  • 4.将自定义对象指定给代理属性。此对象必须符合CLLocationManagerDelegate协议。
  • 5.配置与要使用的服务相关的属性。例如,在获取位置更新时,始终配置distanceFilter和desiredAccuracy属性。
  • 6.调用适当的方法来启动事件的交付。

对于使用的服务,请准确配置与该服务关联的任何属性。Core Location(核心位置)通过在不需要时关闭硬件来积极地管理电源。例如,将位置事件所需的精度设置为1公里,可使位置管理器灵活地关闭GPS硬件,并仅依赖WiFi或蜂窝无线电,这可显著节省电力。

所有与位置和标题相关的更新都将传递到关联的代理对象,该对象是您提供的自定义对象。

CLLocationManager常用属性
@property(assign, nonatomic) CLLocationDegrees headingFilter API_AVAILABLE(ios(3.0)) API_UNAVAILABLE(watchos, tvos, macOS);

属性描述生成新航向事件所需的最小角度变化(以度为单位)。角度距离是相对于上次交付的航向事件测量的。使用值kCLHeadingFilterNone通知所有移动。此属性的默认值为1度。

@property(assign, nonatomic) CLLocationDistance distanceFilter;

属性描述在生成更新事件之前,设备必须水平移动的最小距离(以米为单位)。此距离是相对于先前交付的位置测量的。使用值kCLDistanceFilterNone通知所有移动。此属性的默认值为kCLDistanceFilterNone。此属性仅与标准位置服务一起使用,在监视重要位置更改时不使用。

@property (readonly, nonatomic) CLLocationDistance maximumRegionMonitoringDistance API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS);

属性描述可以指定给区域的最大边界距离。此属性定义从区域中心点允许的最大边界距离。尝试监视距离大于此值的区域将导致位置管理器向委托发送kclerRorrorRegionMonitoringFailure错误。如果区域监视不可用或不受支持,则此属性中的值为-1。

@property (readonly, nonatomic, copy) NSSet<__kindof CLRegion *> *monitoredRegions API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS);

属性描述由所有位置管理器对象(CLLocationManager对象)监视的共享区域集。不能直接将区域添加到此属性。相反,必须通过调用startMonitoringForRegion:方法来注册区域。此属性中的区域由应用程序中CLLocationManager类的所有实例共享。

此集合中的对象不一定与注册时指定的对象相同。系统只维护区域数据本身。因此,唯一标识已注册区域的方法是使用其标识符属性。

位置管理器在应用程序启动之间保存区域数据。如果应用程序被终止然后重新启动,则此属性的内容将重新填充包含先前注册数据的区域对象。

CLLocationManager常用函数
+ (BOOL)locationServicesEnabled API_AVAILABLE(ios(4.0), macos(10.7));

函数描述返回一个布尔值,指示整个设备上是否启用了位置服务。在开始位置更新之前,可以检查此方法的返回值,以确定设备是否启用了位置服务。如果在禁用位置服务时尝试启动位置更新,则位置管理器将向其代理报告错误。用户通常可以通过切换位置服务开关从设置应用程序启用或禁用位置服务。

返回值 : 如果已启用位置服务,则为“YES”;如果未启用位置服务,则为“NO”。

反应了这里的设置:

截屏2021-07-16上午11.51.58.png
+ (CLAuthorizationStatus)authorizationStatus API_AVAILABLE(ios(4.2), macos(10.7));

函数描述返回应用程序使用位置服务的授权状态。给定应用程序的授权状态由系统管理,并由多个因素决定。应用程序必须由用户明确授权使用定位服务,并且定位服务本身当前必须为系统启用。当应用程序首次尝试使用定位服务时,将自动显示用户授权请求。

返回值 : 指示应用程序是否被授权使用位置服务的值。

CLAuthorizationStatus提供的枚举值:

typedef NS_ENUM(int, CLAuthorizationStatus) {
    //用户尚未选择应用程序是否可以使用位置服务。
       //当未确定授权状态时如果应用程序位于前台,请求授权将导致位置管理器提示用户获得权限。
    kCLAuthorizationStatusNotDetermined = 0,

    //由于某些原因(如家长控制)对位置服务的有效限制,此应用程序无权使用位置服务。
    //用户无法更改此状态
    kCLAuthorizationStatusRestricted,

    //拒绝对此应用程序的位置授权
    //用户已明确拒绝对此应用程序的位置授权,或者在设置中全局禁用了位置服务,或者位置服务不可用(如飞行模式)。
    kCLAuthorizationStatusDenied,

    //用户授权应用程序随时启动定位服务的权限。
    //该授权允许使用所有位置服务,并接收位置事件,无应用程序是否正在使用。
    //应用程序可能会通过诸如访问监控、区域监控和重大位置变化监控等监控api被在后台启动。
    kCLAuthorizationStatusAuthorizedAlways API_AVAILABLE(macos(10.12), ios(8.0)),

    //用户已授权仅在使用应用程序时有访问位置的权限
    //此授权允许仅在应用程序正在使用时使用所有位置服务和接收位置事件。
    //若要继续在后台使用位置服务,需要启用连续后台位置更新
    //通过allowsBackgroundLocationUpdates可以启用连续后台位置更新
    kCLAuthorizationStatusAuthorizedWhenInUse API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(macOS),

    //MacOS使用,iOS、tvOS和watchOS上不推荐或禁止使用此值。用户已授权此应用程序使用位置服务。
    kCLAuthorizationStatusAuthorized API_DEPRECATED("Use kCLAuthorizationStatusAuthorizedAlways", ios(2.0, 8.0)) API_AVAILABLE(macos(10.6)) API_UNAVAILABLE(watchos, tvos) = kCLAuthorizationStatusAuthorizedAlways
};

反应了这里的设置:

截屏2021-07-16上午11.59.42.png
- (void)requestWhenInUseAuthorization API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(macOS);

函数描述请求在应用程序位于前台时使用定位服务的权限。当前授权状态为kCLAuthorizationStatusNotDetermined时,此方法将异步运行,并提示用户授予应用程序使用位置服务的权限。用户提示包含应用程序Info.plist文件中NSLocationWhenInUseUsageDescription密钥的文本,调用此方法时需要该密钥的存在。确定状态后,位置管理器将结果传递给代理的locationManager:didChangeAuthorizationStatus:方法。如果当前授权状态不是kCLAuthorizationStatusNotDetermined,则此方法不执行任何操作,也不调用locationManager:didChangeAuthorizationStatus:方法。

在使用定位服务之前,必须调用此方法或requestAlwaysAuthorization方法。如果用户授予应用“使用时”授权,你的应用可以在前台启动大部分(但不是全部)位置服务(应用程序无法自动启动例如区域监视或重要位置更改服务的应用程序的服务)。在前台启动时,如果应用程序已在Xcode项目的“ Capabilities”选项卡中启用后台位置更新,则服务将继续在后台运行。在后台运行应用程序时尝试启动定位服务将失败。当应用程序移动到具有活动位置服务的后台时,系统会在状态栏中显示位置服务指示器。

注 :如不设置 Info.plist文件,控制器会打印提醒:

This app has attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain both “NSLocationAlwaysAndWhenInUseUsageDescription” and “NSLocationWhenInUseUsageDescription” keys with string values explaining to the user how the app uses this data(此应用试图访问隐私敏感数据,但没有使用说明。应用程序的Info.plist必须同时包含“ NSLocationAlwaysAndWhenInUseUsageDescription”和“ NSLocationWhenInUseUsageDescription”键,这些键的字符串值向用户解释应用程序如何使用此数据)

- (void)startUpdatingLocation API_AVAILABLE(watchos(3.0)) API_UNAVAILABLE(tvOS);

函数描述开始生成报告用户当前位置的更新。此方法立即返回。调用此方法将导致位置管理器获得初始位置修复(可能需要几秒钟),并通过调用其locationManager:didUpdateLocations:方法来通知代理。之后,调用方主要在超过distanceFilter属性中的值时生成更新事件。不过,在其他情况下也可能会提供更新,例如硬件收集到更准确的位置读数,则调用方可以发送另一个通知。

连续多次调用此方法不会自动生成新事件。但在中间调用stopUpdateLocation会导致下次调用此方法时发送新的初始事件

如果启动此服务并且应用程序挂起,则系统将停止传递事件,直到应用程序重新开始运行(在前台或后台)。如果应用程序已终止,则新位置事件的传递将完全停止。因此,如果应用需要在后台接收位置事件,它必须在Info.plist文件中包含UIBackgroundModes键(带有位置值)。

除了实现locationManager:didUpdateLocations:方法的委托对象外,还应该实现locationManager:didFailWithError:方法以响应潜在的错误。

- (void)stopUpdatingLocation;

函数描述停止生成位置更新。只要代码不再需要接收与位置相关的事件,就调用此方法。禁用事件传递使接收器可以在没有客户端需要位置数据时禁用适当的硬件(从而节省电源)。始终可以通过再次调用startUpdatingLocation方法重新启动位置更新的生成。

+ (BOOL)headingAvailable API_AVAILABLE(ios(4.0), macos(10.7)) API_UNAVAILABLE(watchos, tvOS);

函数描述返回一个布尔值,指示位置管理器是否能够生成与导航方向相关的事件。在要求位置管理器传递与导航方向相关的事件之前,应检查此方法返回的值。

返回值 : 如果航向数据可用,则为“YES”;如果不可用,则为“NO”。

- (void)startUpdatingHeading API_AVAILABLE(ios(3.0)) API_UNAVAILABLE(watchos, tvos, macOS);

函数描述开始生成报告用户当前导航方向的更新。此方法立即返回。当调用方已经停止导航方向的更新时,调用此方法将导致它获取初始航向并通知代理。此外,当超过headingFilter属性中的值时,调用方生成更新事件。

在调用此方法之前,应始终检查headingAvailable属性,以查看当前设备上是否支持导航方向信息。如果不支持导航方向信息,则调用此方法无效,并且不会将事件传递给代理。

连续多次调用此方法不会自动生成新事件。但在中间调用stopUpdatengHeading会导致下次调用此方法时发送新的初始事件

如果启动此服务并且应用程序挂起,则系统将停止传递事件,直到应用程序重新开始运行(在前台或后台)。如果应用程序已终止,则新航向事件的传递将完全停止,并且必须在应用程序重新启动时由代码重新启动

航向事件将传递到委托的locationManager:didUpdateHeading:方法。如果出现错误,位置管理器将改为调用委托的locationManager:didFailWithError:方法。

- (void)stopUpdatingHeading API_AVAILABLE(ios(3.0)) API_UNAVAILABLE(watchos, tvos, macOS);

函数描述停止生成航向更新。只要代码不再需要接收与航向相关的事件,就调用此方法。禁用事件传递使接收器可以在没有客户端需要位置数据时禁用适当的硬件(从而节省电源)。始终可以通过再次调用startUpdatingHeading方法来重新启动航向更新的生成。

+ (BOOL)isMonitoringAvailableForClass:(Class)regionClass API_AVAILABLE(ios(7.0), macos(10.10)) API_UNAVAILABLE(watchos, tvOS);

函数描述返回一个布尔值,指示设备是否支持使用指定类进行区域监听。区域监视支持的可用性取决于设备上存在的硬件。此方法不考虑位置服务的可用性或用户可能已为应用程序或系统禁用这些服务的事实,必须单独确定应用程序的授权状态。

参数 :

regionClass : MapKit框架中的区域监视类。这个类必须从CLRegion类派生。

返回值 : 如果设备能够使用指定的类监视区域,则为“YES”;否则为“NO”。

- (void)requestStateForRegion:(CLRegion *)region API_AVAILABLE(ios(7.0), macos(10.10)) API_UNAVAILABLE(watchos, tvOS);

函数描述异步检索区域的状态。 此方法异步执行请求,并将结果传递给位置管理器的代理。必须在代理中实现locationManager:dideterminestate:forRegion:方法才能接收结果。如果region参数包含未知类型的region对象,则此方法不执行任何操作。

参数 :

region : 想知道的地区。此对象必须是MapKit提供的标准区域子类之一的实例。不能使用此方法来确定自己定义的自定义区域的状态。

- (void)startMonitoringForRegion:(CLRegion *)region API_AVAILABLE(ios(5.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS);

函数描述开始监视指定区域。对于要监视的每个区域,必须调用此方法一次。如果应用程序已监视具有相同标识符的现有区域,则旧区域将替换为新区域。使用此方法添加的区域由应用程序中的所有CLLocationManager对象共享,并存储在monitoredRegions属性中。

区域事件将传递到代理的locationManager:didEnterRegion: 和locationManager:didExitRegion: 方法。如果出现错误,位置管理器将改为调用代理的locationManager:monitoringDidFailForRegion:withError: 方法。一个应用程序一次最多可以注册20个区域。为了及时报告区域变化,区域监视服务需要网络连接。

参数 :

region : 定义要监视的边界的区域对象。此参数不能为nil。

- (void)stopMonitoringForRegion:(CLRegion *)region API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS);

函数描述停止监视指定区域。如果指定的区域对象当前未被监视,则此方法无效。

参数 :

region : 当前正在监视的区域对象。此参数不能为nil。

CLLocationManager代理函数

- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray *)locations API_AVAILABLE(ios(6.0), macos(10.9));

函数描述通知代理新的位置数据可用。此方法的实现是可选的,但建议使用。

参数 :

manager : 生成更新事件的位置管理器对象。

locations : 包含位置数据的CLLocation对象数组。该数组始终包含至少一个表示当前位置的对象。如果更新被延迟,或者多个位置在更新之前到达,则数组可能包含其他条目。数组中的对象按照它们发生的顺序组织。因此,最近的位置更新位于数组的末尾

- (void)locationManager:(CLLocationManager *)manager
       didUpdateHeading:(CLHeading *)newHeading API_AVAILABLE(ios(3.0)) API_UNAVAILABLE(watchos, tvos, macOS);

函数描述通知代理位置管理器接收到更新的航向信息。此方法的实现是可选的,但如果使用startUpdatingHeading方法开始标题更新,则此实现是预期的。locationManager对象在您最初启动航向服务之后调用这个方法。当先前报告的值更改超过CLLocationManager对象的headingFilter属性中指定的值时,将传递后续事件。

参数 :

manager : 生成更新事件的位置管理器对象。

newHeading : 新的航向数据。

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status API_AVAILABLE(ios(4.2), macos(10.7));

函数描述在应用程序创建位置管理器时以及授权状态更改时通知代理其授权状态。当应用程序创建相关对象的CLLocationManager实例时,以及当应用程序的授权状态更改时,系统调用此方法。状态将通知应用程序是否可以访问用户的位置。

使用此代理方法来管理应用程序的状态更改,以响应其使用位置信息的能力。例如根据需要启用或禁用应用程序的位置相关功能。

Core Location(核心位置)保证会调用locationManager:didChangeAuthorizationStatus:方法,当用户的操作导致授权状态改变时,以及当你的应用程序创建一个CLLocationManager实例时,无论你的应用程序是在前台还是后台运行。

如果在调用requestWhenInUseAuthorization或requestAlwaysAuthorization方法之后,用户的选择没有更改授权状态,则位置管理器不会向该方法报告当前的授权状态,位置管理器只报告更改。例如,当状态从kCLAuthorizationStatusNotDetermined更改为kCLAuthorizationStatusAuthorizedWhenInUse时,位置管理器调用此方法。

参数 :

manager : 报告事件的位置管理器对象。

status : 应用程序的授权状态。

- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager  API_AVAILABLE(ios(3.0)) API_UNAVAILABLE(watchos, tvos, macOS);

函数描述询问代理是否应显示航向校准警报。Core Location(核心位置)可调用此方法,以校准用于确定航向值的机载硬件。通常,Core Location(核心位置)在以下时间调用此方法:

第一次请求航向更新,当磁芯位置观测到所观测磁场的大小或倾角发生显著变化时,如果您从该方法返回YES, Core Location(核心位置)将立即在当前窗口的顶部显示标题校准警告。校准警报提示用户以特定的模式移动设备,以便核心位置能够区分地球磁场和任何局部磁场。在完成校准或通过调用dismissHeadingCalibrationDisplay方法显式地取消警报之前,警报都是可见的。在后一种情况下,可以使用此方法设置计时器,并在指定时间量过后关闭接口。

校准过程只能过滤掉随设备移动的磁场。若要校准靠近其他磁干扰源的设备,用户必须将设备从磁干扰源处移开,或在校准过程中将磁干扰源与设备一起移开。

如果从此方法返回NO或在代理中没有为其提供实现,则核心位置不会显示标题校准警告。即使没有显示警报,当任何干扰磁场远离设备时,校准仍然可以自然发生。然而,如果设备由于任何原因无法校准自身,任何后续事件的headingAccuracy属性值将反映未校准的读数。

参数 :

manager : 协调航向校准警报显示的位置管理器对象。

返回值 : 如果要允许显示航向校准警报,则为“YES”;如果不允许,则为“NO”。

航向校准警报页面大致长成这样 :

IMG_1316.PNG
- (void)locationManager:(CLLocationManager *)manager
    didFailWithError:(NSError *)error;

函数描述通知代理位置管理器无法检索位置值。位置管理器在尝试获取位置或标题数据时遇到错误时调用此方法。如果位置服务无法立即检索位置,它将报告kclerRorrorLocationUnknown错误并继续尝试。在这种情况下,您可以忽略错误并等待新事件。如果由于附近磁场的强烈干扰而无法确定航向,则此方法返回kCLErrorHeadingFailure。

如果用户拒绝应用程序使用位置服务,则此方法将报告一个kCLErrorDenied错误。收到此类错误后,应停止定位服务。

参数 :

manager : 无法检索位置的位置管理器对象。

error : 包含无法检索位置或标题的原因的错误对象。

- (void)locationManager:(CLLocationManager *)manager
    didEnterRegion:(CLRegion *)region API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS);

函数描述通知代理用户已进入指定区域。因为区域是共享的应用程序资源,所以每个活动位置管理器对象都将此消息传递给其关联的代理。哪个位置管理器实际注册了指定的区域并不重要。如果多个位置管理器共享一个代理对象,则该代理将多次接收消息。所提供的区域对象可能与已注册的区域对象不同。因此,永远不要执行对象指针级别的比较来确定是否相等。相反,使用该区域的标识符字符串来确定您的代理是否应该响应。

参数 :

manager : 报告事件的位置管理器对象。

region : 包含所输入区域信息的对象。

- (void)locationManager:(CLLocationManager *)manager
    didExitRegion:(CLRegion *)region API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS);

函数描述通知代理用户已离开指定区域。因为区域是共享的应用程序资源,所以每个活动位置管理器对象都将此消息传递给其关联的代理。哪个位置管理器实际注册了指定的区域并不重要。如果多个位置管理器共享一个代理对象,则该代理将多次接收消息。所提供的区域对象可能与已注册的区域对象不同。因此,永远不要执行对象指针级别的比较来确定是否相等。相反,使用该区域的标识符字符串来确定您的代理是否应该响应。

参数 :

manager : 报告事件的位置管理器对象。

region : 包含有关已离开区域的信息的对象。

- (void)locationManager:(CLLocationManager *)manager
    didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region API_AVAILABLE(ios(7.0), macos(10.10)) API_UNAVAILABLE(watchos, tvOS);

函数描述通知代理指定区域的状态。每当区域有边界转换时,位置管理器都会调用此方法。除了调用locationManager:didEnterRegion:和locationManager:didextregion:方法之外,它还调用此方法。位置管理器还调用此方法以响应对其requestStateForRegion:方法的调用,该方法异步运行。

参数 :

manager : 报告事件的位置管理器对象。

state : 指定区域的状态。

region : 国家确定的地区。

- (void)locationManager:(CLLocationManager *)manager
    monitoringDidFailForRegion:(nullable CLRegion *)region
    withError:(NSError *)error API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS);

函数描述通知代理发生区域监视错误。如果尝试监视给定区域时出错,位置管理器会将此消息发送给其代理。区域监视可能会失败,因为无法监视该区域本身,或者是因为在配置区域监视服务时出现了更常见的故障。
尽管此方法的实现是可选的,但如果在应用程序中使用区域监视,建议实现它。

参数 :

manager : 报告事件的位置管理器对象。

region : 发生错误的区域。

error : 一个错误对象,包含指示区域监视失败原因的错误代码。

- (void)locationManager:(CLLocationManager *)manager
    didStartMonitoringForRegion:(CLRegion *)region API_AVAILABLE(ios(5.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS);

函数描述通知代理正在监视新区域

参数 :

manager : 报告事件的位置管理器对象。

region : 正在监视的区域。

- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager API_AVAILABLE(ios(6.0)) API_UNAVAILABLE(watchos, tvos, macOS);

函数描述通知代理位置更新已暂停。当位置管理器检测到设备的位置没有改变时,它可以暂停更新的传递,以便关闭相应的硬件并节省电源。当它这样做时,它会调用此方法以让应用程序知道发生了这种情况。

暂停发生后,必须在适当的时间重新启动位置服务。可以使用此方法的实现在用户的当前位置启动区域监视,或启用“访问位置”服务来确定用户何时开始再次移动。另一种选择是立即以较低的精度重新启动定位服务(这可以节省能源),然后只有在用户再次开始移动后才能恢复较高的精度。

参数 :

manager : 暂停事件传递的位置管理器对象。

- (void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager API_AVAILABLE(ios(6.0)) API_UNAVAILABLE(watchos, tvos, macOS);

函数描述通知代理位置更新的传递已恢复。当在自动暂停后重启定位服务时,Core location会调用这个方法来通知你的应用服务已经恢复。Core location不会在暂停更新后自动恢复更新,程序要负责重新启动你的应用程序中的定位服务。

参数 :

manager : 恢复事件传递的位置管理器。

- (void)locationManager:(CLLocationManager *)manager
    didFinishDeferredUpdatesWithError:(nullable NSError *)error API_AVAILABLE(ios(6.0), macos(10.9)) API_UNAVAILABLE(watchos, tvOS);

函数描述通知代理不再延迟更新。CLLocationManager对象调用此方法以让应用程序知道它已停止延迟位置事件的传递。管理器可能出于多种原因调用此方法。例如,当您完全停止位置更新、要求位置管理器禁止延迟更新或满足延迟更新的条件(例如超过超时或距离参数)时,它将调用此函数。

参数 :

manager : 生成更新事件的位置管理器对象。

error : 包含无法传递延迟位置更新原因的错误对象。

CLGeocoder - 地理编码

用于在地理坐标和地名之间转换的接口。提供在坐标(指定为经纬度)和该坐标的地名之间进行转换的服务。坐标的地名表示通常由与给定位置相对应的街道、城市、州和国家信息组成,但也可以包含相关的兴趣点、地标或其他识别信息。geocoder对象是一个单一对象,它与基于网络的服务一起查找指定坐标值的地标信息

若要使用地理编码器对象,请创建该对象并调用其正向或反向地理编码方法之一以开始请求。反向地理编码请求采用纬度和经度值,查找到用户可读的地址。正向地理编码请求采用用户可读的地址,并找到相应的纬度和经度值。正向地理编码请求还可以返回有关指定位置的附加信息,例如该位置的兴趣点或建筑物。对于这两种类型的请求,使用CLPlacemark对象返回结果。在正向地理编码请求的情况下,如果提供的信息产生多个可能的位置,则可以返回多个placemark对象。为了明智地决定返回什么类型的信息,geocoder服务器在处理请求时使用提供给它的所有信息。例如,如果用户在高速公路上快速移动,它可能返回整个区域的名称,而不是用户经过的小公园的名称。

CLGeocoder常用函数
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;

函数描述为指定的位置提交反向地理编码请求,此方法将指定的位置数据异步提交给地理编码服务器并返回。当请求完成时,geocoder在主线程上执行提供的完成处理程序。

启动反向地理编码请求后,不要尝试启动另一个反向或正向地理编码请求。地理编码请求对每个应用程序都有速率限制,因此在短时间内发出太多请求可能会导致某些请求失败。当超过最大速率时,geocoder会将值为kCLErrorNetwork的错误对象传递给完成处理程序。

参数 :

location : 包含要查找的坐标数据的位置对象。

completionHandler : 要与结果一起执行的处理程序块。无论请求是成功还是失败,geocoder都会执行此处理程序。

- (void)reverseGeocodeLocation:(CLLocation *)location preferredLocale:(nullable NSLocale *)locale completionHandler:(CLGeocodeCompletionHandler)completionHandler API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0));

函数描述提交指定位置和区域设置的反向地理编码请求。此方法将指定的位置数据异步提交给地理编码服务器并返回。当请求完成时,geocoder在主线程上执行提供的完成处理程序。

启动反向地理编码请求后,不要尝试启动另一个反向或正向地理编码请求。地理编码请求对每个应用程序都有速率限制,因此在短时间内发出太多请求可能会导致某些请求失败。当超过最大速率时,geocoder会将带有值network的错误对象传递给完成处理程序。

参数 :

location : 包含要查找的坐标数据的位置对象。

locale : 返回地址信息时要使用的区域设置。如果希望在不同于用户当前语言设置的区域设置中返回地址,可以为此参数指定一个值。指定nil以使用用户的默认区域设置信息。

completionHandler : 要与结果一起执行的处理程序块。无论请求是成功还是失败,geocoder都会执行此处理程序。

- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;

函数描述使用指定的字符串提交正向地理编码请求。此方法将指定的位置数据异步提交给地理编码服务器并返回。完成处理程序块将在主线程上执行。

启动前向地理编码请求后,不要尝试启动另一个前向或反向地理编码请求。地理编码请求对每个应用程序都有速率限制,因此在短时间内发出太多请求可能会导致某些请求失败。当超过最大速率时,geocoder会将值为kCLErrorNetwork的错误对象传递给完成处理程序。

参数 :

addressString : 描述要查找的位置的字符串。

completionHandler : 要与结果一起执行的处理程序块。无论请求是成功还是失败,geocoder都会执行此处理程序。

- (void)geocodeAddressString:(NSString *)addressString inRegion:(nullable CLRegion *)region completionHandler:(CLGeocodeCompletionHandler)completionHandler;

函数描述使用指定的字符串和区域信息提交正向地理编码请求。此方法将指定的位置数据异步提交给地理编码服务器并返回。完成处理程序块将在主线程上执行。

启动前向地理编码请求后,不要尝试启动另一个前向或反向地理编码请求。地理编码请求对每个应用程序都有速率限制,因此在短时间内发出太多请求可能会导致某些请求失败。当超过最大速率时,geocoder会将值为kCLErrorNetwork的错误对象传递给完成处理程序。

参数 :

addressString : 描述要查找的位置的字符串。

region : 查找指定地址时用作提示的地理区域。通过指定一个区域,可以将返回的结果集的优先级设置为靠近某个特定地理区域(通常是用户的当前位置)的位置。如果应用程序被授权提供位置服务,并且您为此参数指定了nil,则结果集将根据用户的大致位置进行优先级排序。调用此方法不会触发位置服务授权请求。

completionHandler : 要与结果一起执行的处理程序块。无论请求是成功还是失败,geocoder都会执行此处理程序。

- (void)geocodeAddressString:(NSString *)addressString inRegion:(nullable CLRegion *)region preferredLocale:(nullable NSLocale *)locale completionHandler:(CLGeocodeCompletionHandler)completionHandler API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0));

函数描述使用指定的地址字符串和区域设置信息提交正向地理编码请求。此方法将指定的位置数据异步提交给地理编码服务器并返回。当请求完成时,geocoder在主线程上执行提供的完成处理程序。

启动正向地理编码请求后,不要尝试启动另一个反向或正向地理编码请求。地理编码请求对每个应用程序都有速率限制,因此在短时间内发出太多请求可能会导致某些请求失败。当超过最大速率时,geocoder会将带有值network的错误对象传递给完成处理程序。

参数 :

addressString : 描述要查找的位置的字符串。

region : 查找指定地址时用作提示的地理区域。通过指定一个区域,可以将返回的结果集的优先级设置为靠近某个特定地理区域(通常是用户的当前位置)的位置。如果应用程序被授权提供位置服务,并且您为此参数指定了nil,则结果集将根据用户的大致位置进行优先级排序。调用此方法不会触发位置服务授权请求。

locale : 地址字符串的区域设置。指定nil以使用用户的当前区域设置。

completionHandler : 要与结果一起执行的处理程序块。无论请求是成功还是失败,geocoder都会执行此处理程序。

- (void)cancelGeocode;

函数描述取消挂起的地理编码请求。可以使用此方法取消挂起的请求并释放与该请求关联的资源。取消挂起的请求将导致调用完成处理程序块。如果请求未挂起,因为它已返回或尚未开始,则此方法不执行任何操作。

//
//  CLLocation+YCLocation.h
//  YCMapViewDemo
//
//  Created by gongliang on 13-9-16.
//  Copyright (c) 2013年 gongliang. All rights reserved.
//  火星坐标系转换扩展
/*
 从 CLLocationManager 取出来的经纬度放到 mapView 上显示,是错误的!
 从 CLLocationManager 取出来的经纬度去 Google Maps API 做逆地址解析,当然是错的!
 从 MKMapView 取出来的经纬度去 Google Maps API 做逆地址解析终于对了。去百度地图API做逆地址解析,依旧是错的!
 从上面两处取的经纬度放到百度地图上显示都是错的!错的!的!
 
 分为 地球坐标,火星坐标(iOS mapView 高德 , 国内google, 搜搜、阿里云 都是火星坐标),百度坐标(百度地图数据主要都是四维图新提供的)
 
 火星坐标: MKMapView
 地球坐标: CLLocationManager
 
 当用到CLLocationManager 得到的数据转化为火星坐标, MKMapView不用处理
 
 
 API                坐标系
 百度地图API         百度坐标
 腾讯搜搜地图API      火星坐标
 搜狐搜狗地图API      搜狗坐标
 阿里云地图API       火星坐标
 图吧MapBar地图API   图吧坐标
 高德MapABC地图API   火星坐标
 灵图51ditu地图API   火星坐标
 
 */

#import 

@interface CLLocation (YCLocation)

///从地图坐标转化到火星坐标
- (CLLocation*)locationMarsFromEarth;

///从火星坐标转化到百度坐标
- (CLLocation*)locationBaiduFromMars;

///从百度坐标到火星坐标
- (CLLocation*)locationMarsFromBaidu;

@end

//
//  CLLocation+YCLocation.m
//  YCMapViewDemo
//
//  Created by gongliang on 13-9-16.
//  Copyright (c) 2013年 gongliang. All rights reserved.
//

#import "CLLocation+YCLocation.h"

void transform_earth_from_mars(double lat, double lng, double* tarLat, double* tarLng);
void transform_mars_from_baidu(double lat, double lng, double* tarLat, double* tarLng);
void transform_baidu_from_mars(double lat, double lng, double* tarLat, double* tarLng);

@implementation CLLocation (YCLocation)

///从地图坐标转化到火星坐标
- (CLLocation*)locationMarsFromEarth
{
    double lat = 0.0;
    double lng = 0.0;
    transform_earth_from_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*)locationBaiduFromMars
{
    double lat = 0.0;
    double lng = 0.0;
    transform_mars_from_baidu(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*)locationMarsFromBaidu
{
    double lat = 0.0;
    double lng = 0.0;
    transform_baidu_from_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;
}

@end



// --- transform_earth_from_mars ---
// 参考来源:https://on4wp7.codeplex.com/SourceControl/changeset/view/21483#353936
// Krasovsky 1940
//
// a = 6378245.0, 1/f = 298.3
// b = a * (1 - f)
// ee = (a^2 - b^2) / a^2;

const double a = 6378245.0;
const double ee = 0.00669342162296594323;

bool transform_sino_out_china(double lat, double lon)
{
    if (lon < 72.004 || lon > 137.8347)
        return true;
    if (lat < 0.8293 || lat > 55.8271)
        return true;
    return false;
}

double transform_earth_from_mars_lat(double x, double y)
{
    double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x));
    ret += (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0;
    ret += (20.0 * sin(y * M_PI) + 40.0 * sin(y / 3.0 * M_PI)) * 2.0 / 3.0;
    ret += (160.0 * sin(y / 12.0 * M_PI) + 320 * sin(y * M_PI / 30.0)) * 2.0 / 3.0;
    return ret;
}

double transform_earth_from_mars_lng(double x, double y)
{
    double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x));
    ret += (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0;
    ret += (20.0 * sin(x * M_PI) + 40.0 * sin(x / 3.0 * M_PI)) * 2.0 / 3.0;
    ret += (150.0 * sin(x / 12.0 * M_PI) + 300.0 * sin(x / 30.0 * M_PI)) * 2.0 / 3.0;
    return ret;
}

void transform_earth_from_mars(double lat, double lng, double* tarLat, double* tarLng)
{
    if (transform_sino_out_china(lat, lng))
    {
        *tarLat = lat;
        *tarLng = lng;
        return;
    }
    double dLat = transform_earth_from_mars_lat(lng - 105.0, lat - 35.0);
    double dLon = transform_earth_from_mars_lng(lng - 105.0, lat - 35.0);
    double radLat = lat / 180.0 * M_PI;
    double magic = sin(radLat);
    magic = 1 - ee * magic * magic;
    double sqrtMagic = sqrt(magic);
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * M_PI);
    dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * M_PI);
    *tarLat = lat + dLat;
    *tarLng = lng + dLon;
}

// --- transform_earth_from_mars end ---
// --- transform_mars_vs_bear_paw ---
// 参考来源:http://blog.woodbunny.com/post-68.html
const double x_pi = M_PI * 3000.0 / 180.0;

void transform_mars_from_baidu(double gg_lat, double gg_lon, double *bd_lat, double *bd_lon)
{
    double x = gg_lon, y = gg_lat;
    double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi);
    double theta = atan2(y, x) + 0.000003 * cos(x * x_pi);
    *bd_lon = z * cos(theta) + 0.0065;
    *bd_lat = z * sin(theta) + 0.006;
}

void transform_baidu_from_mars(double bd_lat, double bd_lon, double *gg_lat, double *gg_lon)
{
    double x = bd_lon - 0.0065, y = bd_lat - 0.006;
    double z = sqrt(x * x + y * y) - 0.00002 * sin(y * x_pi);
    double theta = atan2(y, x) - 0.000003 * cos(x * x_pi);
    *gg_lon = z * cos(theta);
    *gg_lat = z * sin(theta);
}

//
//  TestCLLocationController.h
//  FrameworksTest

#import 

@interface TestCLLocationController : UIViewController

@end
//
//  TestCLLocationController.m
//  FrameworksTest

#import "TestCLLocationController.h"
#import "CLLocation+YCLocation.h"

@interface TestCLLocationController ()

@property (nonatomic, strong) CLLocationManager *locationManager;//位置管理对象
@property (nonatomic, strong) CLLocation *location;//位置对象
@property (nonatomic, strong) UIImageView *headingRelatedImageView;//航向指示视图
@property (nonatomic, strong) UITextField *addressTextField;//地址文本输入框
@property (nonatomic, strong) UILabel *longitudeLabel;//经度值标签
@property (nonatomic, strong) UILabel *latitudeLabel;//纬度值标签
@property (nonatomic, strong) UITextField *longitudeTextField;//经度文本输入框
@property (nonatomic, strong) UITextField *latitudeTextField;//纬度文本输入框
@property (nonatomic, strong) UILabel *addressLabel;//具体地址标签
@property (nonatomic, strong) UILabel *regionMonitorLabel;//区域监视标签
@property (nonatomic, strong) UILabel *monitoringLabel;//正在区域监视标签
@property (nonatomic, strong) UILabel *locationUpdateLabel;//位置更新标签

@end

@implementation TestCLLocationController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [self createNavigationTitleView:@"CLLocation"];
    //请求定位权限
    [self requestLocationJurisdiction];
    //设置航向指示视图
    [self setHeadingRelatedView];
    //设置区域监听
    [self setAreaMonitoring];
    //设置视图
    [self createView];
    
}

///请求定位权限
- (void)requestLocationJurisdiction{
    //判断设备在设置中是否启用了位置服务
    if([CLLocationManager locationServicesEnabled]){
        //设备启用了位置服务,判断应用程序的位置权限
        switch ([CLLocationManager authorizationStatus]) {
            //用户尚未选择应用程序是否可以使用位置服务
            case kCLAuthorizationStatusNotDetermined:
                //启用定位
                [self enablePositioning];
                //请求在应用程序位于前台时使用定位服务的权限
                [self.locationManager requestWhenInUseAuthorization];
                break;
            //用户授权应用程序随时启动定位服务的权限
            //用户已授权仅在使用应用程序时有访问位置的权限
            case kCLAuthorizationStatusAuthorizedAlways:
            case kCLAuthorizationStatusAuthorizedWhenInUse:
                //启用定位
                [self enablePositioning];
                break;
            //拒绝对此应用程序的位置授权
            case kCLAuthorizationStatusDenied:
                //设备未启用位置服务,展示提示
                [self showPermissionSettingAlert:@"无法定位" withMessage:@"当前应用程序设置不支持定位"];
                break;
            default:
                break;
        }
        
    }else{
        //设备未启用位置服务,展示提示
        [self showPermissionSettingAlert:@"无法定位" withMessage:@"当前设备设置不支持定位"];
    }
}

///启用定位
- (void)enablePositioning{
    //初始化位置管理对象
    if(self.locationManager == nil){
        self.locationManager = [[CLLocationManager alloc]init];
    }
    //设置代理
    self.locationManager.delegate = self;
    //请求在应用程序位于前台时使用定位服务的权限
    [self.locationManager requestAlwaysAuthorization];
    //开始生成报告用户当前位置的更新
    [self.locationManager startUpdatingLocation];
    //判断位置管理器是否能够生成与导航方向相关的事件
    if ([CLLocationManager headingAvailable]) {
        //生成新航向事件所需的最小角度变化
        self.locationManager.headingFilter = kCLHeadingFilterNone;
        [self.locationManager startUpdatingHeading];
    }
}

///显示权限设置提示框
- (void)showPermissionSettingAlert:(NSString *)title withMessage:(NSString *)message{
    //初始化提示框
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    //YES操作项
    UIAlertAction *yesAction = [UIAlertAction actionWithTitle:@"YES" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
        //打开设置页面
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]options:@{}completionHandler:nil];
    }];
    //NO操作项
    UIAlertAction *noAction = [UIAlertAction actionWithTitle:@"NO" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
    }];
    [alertController addAction:yesAction];
    [alertController addAction:noAction];
    [self presentViewController:alertController animated:true completion:nil];
}

///设置航向指示视图
- (void)setHeadingRelatedView{
    //航向指示视图
    self.headingRelatedImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"btn_navigation"]];
    self.headingRelatedImageView.contentMode = UIViewContentModeScaleToFill;
    [self.view addSubview:self.headingRelatedImageView];
    //Masonry布局
    [self.headingRelatedImageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.view.mas_top).offset(HEAD_BAR_HEIGHT);
        make.centerX.equalTo(self.view);
        make.size.mas_equalTo(CGSizeMake(20, 30));
    }];
}

///设置区域监听
- (void)setAreaMonitoring{
    //判断设备是否支持区域监听
    if (![CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
        return;
    }
    //指定区域类型,一般是圆形区域
    //区域的中点坐标
    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(39.909502, 119.511151);
    //区域半径(米)
    CLLocationDistance distance = 170.0;
    //半径限制
    if (distance > self.locationManager.maximumRegionMonitoringDistance) {
        distance = self.locationManager.maximumRegionMonitoringDistance;
    }
    //初始化圆形地理区域,指定为中心点和半径。
    CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:distance identifier:@"MyLocation"];
    //开始监视指定区域,对于要监视的每个区域,必须调用此方法一次
    [self.locationManager startMonitoringForRegion:region];
    //延迟2秒执行,以防止概率出现的Domain=kCLErrorDomain Code=5错误
    dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
    dispatch_after(delayTime, dispatch_get_main_queue(), ^{
        //异步检索区域的状态
        [self.locationManager requestStateForRegion:region];
    });
    
}

///设置视图
- (void)createView{
    //初始化地址提示标签
    UILabel *addressTipsLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    addressTipsLabel.text = @"地址:";
    addressTipsLabel.font = [UIFont systemFontOfSize:15];
    [self.view addSubview:addressTipsLabel];
    //Masonry布局
    [addressTipsLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.headingRelatedImageView.mas_bottom).offset(20);
        make.left.equalTo(self.view.mas_left).offset(20);
    }];
    
    //初始化地址文本输入框
    self.addressTextField = [[UITextField alloc]initWithFrame:CGRectZero];
    self.addressTextField.placeholder = @" 请输入地址";
    self.addressTextField.layer.borderColor = [UIColor blackColor].CGColor;
    self.addressTextField.layer.borderWidth = 0.5;
    [self.view addSubview:self.addressTextField];
    //Masonry布局
    [self.addressTextField mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(addressTipsLabel);
        make.left.equalTo(addressTipsLabel.mas_right).offset(10);
        make.size.mas_equalTo(CGSizeMake(200, 35));
    }];
    
    //初始化查询按钮
    UIButton *queryButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [queryButton setTitle:@"查询" forState:UIControlStateNormal];
    [queryButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [queryButton setBackgroundColor:[UIColor blueColor]];
    [queryButton setTag:1000];
    [self.view addSubview:queryButton];
    [queryButton addTarget:self action:@selector(queryButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    //Masonry布局
    [queryButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.addressTextField);
        make.left.equalTo(self.addressTextField.mas_right).offset(10);
        make.size.mas_equalTo(CGSizeMake(60, 35));
    }];
    
    //初始化经度值标签
    self.longitudeLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    self.longitudeLabel.font = [UIFont systemFontOfSize:15];
    self.longitudeLabel.textColor = [UIColor redColor];
    self.longitudeLabel.textAlignment = NSTextAlignmentCenter;
    self.longitudeLabel.text = @"当前经度";
    self.longitudeLabel.layer.borderWidth = 0.5;
    self.longitudeLabel.layer.borderColor = [UIColor redColor].CGColor;
    [self.view addSubview:self.longitudeLabel];
    [self.longitudeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(addressTipsLabel.mas_bottom).offset(30);
        make.left.equalTo(self.view.mas_left).offset(20);
        make.size.mas_equalTo(CGSizeMake(150, 30));
    }];
    
    //初始化纬度值标签
    self.latitudeLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    self.latitudeLabel.font = [UIFont systemFontOfSize:15];
    self.latitudeLabel.textColor = [UIColor greenColor];
    self.latitudeLabel.text = @"当前纬度";
    self.latitudeLabel.textAlignment = NSTextAlignmentCenter;
    self.latitudeLabel.layer.borderWidth = 0.5;
    self.latitudeLabel.layer.borderColor = [UIColor greenColor].CGColor;
    [self.view addSubview:self.latitudeLabel];
    [self.latitudeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.longitudeLabel);
        make.right.equalTo(queryButton.mas_right).offset(10);
        make.size.mas_equalTo(CGSizeMake(150, 30));
    }];
    
    //初始化经度提示标签
    UILabel *longitudeTipsLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    longitudeTipsLabel.text = @"经度:";
    longitudeTipsLabel.font = [UIFont systemFontOfSize:15];
    [self.view addSubview:longitudeTipsLabel];
    //Masonry布局
    [longitudeTipsLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.longitudeLabel.mas_bottom).offset(50);
        make.left.equalTo(self.view.mas_left).offset(20);
    }];
    
    //初始化经度文本输入框
    self.longitudeTextField = [[UITextField alloc]initWithFrame:CGRectZero];
    self.longitudeTextField.placeholder = @" 请输入经度";
    self.longitudeTextField.layer.borderColor = [UIColor blackColor].CGColor;
    self.longitudeTextField.layer.borderWidth = 0.5;
    [self.view addSubview:self.longitudeTextField];
    //Masonry布局
    [self.longitudeTextField mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(longitudeTipsLabel);
        make.left.equalTo(addressTipsLabel.mas_right).offset(10);
        make.size.mas_equalTo(CGSizeMake(105, 35));
    }];
    
    //初始化纬度提示标签
    UILabel *latitudeTipsLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    latitudeTipsLabel.text = @"纬度:";
    latitudeTipsLabel.font = [UIFont systemFontOfSize:15];
    [self.view addSubview:latitudeTipsLabel];
    //Masonry布局
    [latitudeTipsLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(longitudeTipsLabel);
        make.left.equalTo(self.latitudeLabel.mas_left);
    }];
    
    //初始化经度文本输入框
    self.latitudeTextField = [[UITextField alloc]initWithFrame:CGRectZero];
    self.latitudeTextField.placeholder = @" 请输入纬度";
    self.latitudeTextField.layer.borderColor = [UIColor blackColor].CGColor;
    self.latitudeTextField.layer.borderWidth = 0.5;
    [self.view addSubview:self.latitudeTextField];
    //Masonry布局
    [self.latitudeTextField mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(latitudeTipsLabel);
        make.left.equalTo(latitudeTipsLabel.mas_right).offset(10);
        make.size.mas_equalTo(CGSizeMake(105, 35));
    }];
    
    //初始化查询按钮
    UIButton *queryButton2 = [UIButton buttonWithType:UIButtonTypeCustom];
    [queryButton2 setTitle:@"查询" forState:UIControlStateNormal];
    [queryButton2 setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [queryButton2 setBackgroundColor:[UIColor blueColor]];
    [queryButton2 setTag:1001];
    [self.view addSubview:queryButton2];
    [queryButton2 addTarget:self action:@selector(queryButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    //Masonry布局
    [queryButton2 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.longitudeTextField.mas_bottom).offset(20);
        make.centerX.equalTo(self.view);
        make.size.mas_equalTo(CGSizeMake(120, 35));
    }];
    
    //初始化具体地址标签
    self.addressLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    self.addressLabel.font = [UIFont systemFontOfSize:15];
    self.addressLabel.textColor = [UIColor redColor];
    self.addressLabel.textAlignment = NSTextAlignmentCenter;
    self.addressLabel.text = @"当前位置:";
    self.addressLabel.layer.borderWidth = 0.5;
    self.addressLabel.layer.borderColor = [UIColor redColor].CGColor;
    [self.view addSubview:self.addressLabel];
    //Masonry布局
    [self.addressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(queryButton2.mas_bottom).offset(20);
        make.left.equalTo(self.longitudeLabel.mas_left);
        make.right.equalTo(self.latitudeLabel.mas_right);
        make.height.mas_equalTo(30);
    }];
    
    //初始化区域监视标签
    self.regionMonitorLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    self.regionMonitorLabel.font = [UIFont systemFontOfSize:15];
    self.regionMonitorLabel.textColor = [UIColor blackColor];
    self.regionMonitorLabel.textAlignment = NSTextAlignmentCenter;
    self.regionMonitorLabel.numberOfLines = 0;
    self.regionMonitorLabel.layer.borderWidth = 0.5;
    self.regionMonitorLabel.layer.borderColor = [UIColor blackColor].CGColor;
    [self.view addSubview:self.regionMonitorLabel];
    //Masonry布局
    [self.regionMonitorLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.addressLabel.mas_bottom).offset(30);
        make.left.equalTo(self.longitudeLabel.mas_left);
        make.right.equalTo(self.view.mas_right).offset(- 20);
    }];
    
    //初始化正在区域监视标签
    self.monitoringLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    self.monitoringLabel.font = [UIFont systemFontOfSize:15];
    self.monitoringLabel.textColor = [UIColor blackColor];
    self.monitoringLabel.textAlignment = NSTextAlignmentCenter;
    self.monitoringLabel.numberOfLines = 0;
    self.monitoringLabel.layer.borderWidth = 0.5;
    self.monitoringLabel.layer.borderColor = [UIColor blackColor].CGColor;
    [self.view addSubview:self.monitoringLabel];
    //Masonry布局
    [self.monitoringLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.regionMonitorLabel.mas_bottom).offset(30);
        make.left.equalTo(self.longitudeLabel.mas_left);
        make.right.equalTo(self.view.mas_right).offset(- 20);
    }];
    
    //初始化位置更新标签
    self.locationUpdateLabel = [[UILabel alloc]initWithFrame:CGRectZero];
    self.locationUpdateLabel.font = [UIFont systemFontOfSize:15];
    self.locationUpdateLabel.textColor = [UIColor blackColor];
    self.locationUpdateLabel.textAlignment = NSTextAlignmentCenter;
    self.locationUpdateLabel.numberOfLines = 0;
    self.locationUpdateLabel.layer.borderWidth = 0.5;
    self.locationUpdateLabel.layer.borderColor = [UIColor blackColor].CGColor;
    [self.view addSubview:self.locationUpdateLabel];
    //Masonry布局
    [self.locationUpdateLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.monitoringLabel.mas_bottom).offset(30);
        make.left.equalTo(self.longitudeLabel.mas_left);
        make.right.equalTo(self.view.mas_right).offset(- 20);
    }];
}

///查询按钮点击事件
- (void)queryButtonClick:(UIButton *)sender{
    
    if(sender.tag == 1000 ){
        
        //初始化地理编码器对象
        CLGeocoder *geoCoder = [[CLGeocoder alloc]init];
        //提交指定位置的正向地理编码请求
        [geoCoder geocodeAddressString:self.addressTextField.text completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {
            
            if (placemarks.count >0) {
                
                CLPlacemark *placeMark = placemarks[0];
                CLLocation *location = placeMark.location;
                //设置经度值标签的经度
                self.longitudeLabel.text = [NSString stringWithFormat:@"当前经度:%f",location.coordinate.longitude];
                //设置维度值标签的纬度
                self.latitudeLabel.text = [NSString stringWithFormat:@"当前纬度:%f",location.coordinate.latitude];
                
            }else if(error == nil && placemarks.count){
                
                NSLog(@"无位置和错误返回");
                self.longitudeLabel.text = @"未找到";
                self.latitudeLabel.text = @"未找到";
                
            }else if(error){
                
                NSLog(@"loction error:%@",error);
                self.longitudeLabel.text = @"未找到";
                self.latitudeLabel.text = @"未找到";
                
            }
        }];

        
    }else if(sender.tag == 1001){
        
        if(IS_NOT_EMPTY(self.longitudeTextField.text) && IS_NOT_EMPTY(self.latitudeTextField.text)){
            CLLocationDegrees latitude = self.latitudeTextField.text.doubleValue;
            CLLocationDegrees longitude = self.longitudeTextField.text.doubleValue;
            self.location = [[CLLocation alloc]initWithLatitude:latitude longitude:longitude];
        }
        //初始化地理编码器对象
        CLGeocoder *geoCoder = [[CLGeocoder alloc]init];
        //提交指定位置的反向地理编码请求
        [geoCoder reverseGeocodeLocation:self.location completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {
            if (placemarks.count >0) {

                CLPlacemark *placeMark = placemarks[0];
                NSLog(@"当前国家 - %@",placeMark.country);//当前国家
                NSLog(@"当前城市 - %@",placeMark.locality);//当前城市
                NSLog(@"当前位置 - %@",placeMark.subLocality);//当前位置
                NSLog(@"当前街道 - %@",placeMark.thoroughfare);//当前街道
                NSLog(@"具体地址 - %@",placeMark.name);//具体地址
                self.addressLabel.text = [NSString stringWithFormat:@"%@%@%@,%@",IS_NOT_EMPTY(placeMark.locality)? placeMark.locality : @"",IS_NOT_EMPTY(placeMark.subLocality)? placeMark.subLocality : @"" ,IS_NOT_EMPTY(placeMark.thoroughfare)? placeMark.thoroughfare : @"",IS_NOT_EMPTY(placeMark.name)? placeMark.name : @""];

            }else if(error == nil && placemarks.count){

                NSLog(@"无位置和错误返回");

            }else if(error){

                NSLog(@"loction error:%@",error);

            }

        }];
    }
    
}

#pragma mark - CLLocationManagerDelegate

///通知代理新的位置数据可用
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
    self.location = [locations lastObject];
    if (self.location.coordinate.latitude <= 0 || self.location.coordinate.longitude <= 0) {
        return;
    }
    CLLocationCoordinate2D locationCoordinate = self.location.coordinate;
    //设置经度值标签的经度
    self.longitudeLabel.text = [NSString stringWithFormat:@"当前经度:%f",locationCoordinate.longitude];
    //设置维度值标签的纬度
    self.latitudeLabel.text = [NSString stringWithFormat:@"当前纬度:%f",locationCoordinate.latitude];
    //停止生成位置更新
    [manager stopUpdatingLocation];
    
}

///通知代理位置管理器接收到更新的航向信息
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
    
    CGAffineTransform transform =CGAffineTransformMakeRotation((newHeading.magneticHeading) * M_PI / 180.f );
    self.headingRelatedImageView.transform = transform;
}

///当此应用程序的授权状态更改时调用
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status API_AVAILABLE(ios(4.2), macos(10.7)){
    NSLog(@"当此应用程序的授权状态更改时调用");
    switch (status) {
        //用户授权应用程序随时启动定位服务的权限
        //用户已授权仅在使用应用程序时有访问位置的权限
        case kCLAuthorizationStatusAuthorizedAlways:
        case kCLAuthorizationStatusAuthorizedWhenInUse:
            //启用定位
            [self enablePositioning];
            break;
        default:
            break;
    }
}

///询问代理是否应显示航向校准警报
- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager{
    return YES;
}

///通知代理位置管理器无法检索位置值
- (void)locationManager:(CLLocationManager *)manager
       didFailWithError:(NSError *)error{
    NSLog(@"发生异常了");
    if(error.code == kCLErrorLocationUnknown) {
        NSLog(@"无法检索位置");
    }
    else if(error.code == kCLErrorNetwork) {
        NSLog(@"网络问题");
    }
    else if(error.code == kCLErrorDenied) {
        NSLog(@"定位权限的问题");
    }
}

///通知代理用户已进入指定区域
- (void)locationManager:(CLLocationManager *)manager
         didEnterRegion:(CLRegion *)region API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS){
    //异步检索区域的状态
    [self.locationManager requestStateForRegion:region];
    NSLog(@"以进入指定区域");
    self.regionMonitorLabel.text = @"以进入指定区域";
}

///通知代理用户已离开指定区域
- (void)locationManager:(CLLocationManager *)manager
          didExitRegion:(CLRegion *)region API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS){
    //异步检索区域的状态
    [self.locationManager requestStateForRegion:region];
    NSLog(@"以离开指定区域");
    self.regionMonitorLabel.text = @"以离开指定区域";
}

///通知代理指定区域的状态
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
    
    if (state == CLRegionStateInside) {
        NSLog(@"以进入指定区域");
        self.regionMonitorLabel.text = @"以进入指定区域";
    } else if (state == CLRegionStateOutside) {
        NSLog(@"以离开指定区域");
        self.regionMonitorLabel.text = @"以离开指定区域";
    } else {
        NSLog(@"未知状态");
        self.regionMonitorLabel.text = @"未知状态";
    }
    
}

///通知代理发生区域监视错误
- (void)locationManager:(CLLocationManager *)manager
    monitoringDidFailForRegion:(nullable CLRegion *)region
              withError:(NSError *)error API_AVAILABLE(ios(4.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS){
    //停止区域监视
    [self.locationManager stopMonitoringForRegion:region];
    //CLCircularRegion类定义圆形地理区域的位置和边界
    CLCircularRegion  *circularRegion = (CLCircularRegion *)region;
    CLLocation *location = [[CLLocation alloc]initWithLatitude:circularRegion.center.latitude longitude:circularRegion.center.longitude];
    
    //初始化地理编码器对象
    CLGeocoder *geocoder = [[CLGeocoder alloc]init];
    //提交指定位置的反向地理编码请求
    [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {
        
        if (placemarks.count >0) {

                CLPlacemark *placeMark = placemarks[0];
                NSLog(@"当前国家 - %@",placeMark.country);//当前国家
                NSLog(@"当前城市 - %@",placeMark.locality);//当前城市
                NSLog(@"当前位置 - %@",placeMark.subLocality);//当前位置
                NSLog(@"当前街道 - %@",placeMark.thoroughfare);//当前街道
                NSLog(@"具体地址 - %@",placeMark.name);//具体地址
                NSString *errorGeocoder = [NSString stringWithFormat:@"%@%@%@,%@",IS_NOT_EMPTY(placeMark.locality)? placeMark.locality : @"",IS_NOT_EMPTY(placeMark.subLocality)? placeMark.subLocality : @"" ,IS_NOT_EMPTY(placeMark.thoroughfare)? placeMark.thoroughfare : @"",IS_NOT_EMPTY(placeMark.name)? placeMark.name : @""];
            
                NSLog(@"%@区域监听发生错误",errorGeocoder);

            }else if(error == nil && placemarks.count){

                NSLog(@"无位置和错误返回");

            }else if(error){

                NSLog(@"loction error:%@",error);

            }

        }];
    //输出包含错误的本地化描述的字符串
    NSLog(@"区域监控失败 %@ %@",region, error.localizedDescription);
    self.regionMonitorLabel.text = error.localizedDescription;
    
    for (CLRegion *monitoredRegion in manager.monitoredRegions) {
        NSLog(@"monitoredRegion: %@", monitoredRegion);
    }
    if ((error.domain != kCLErrorDomain || error.code != 5) &&
        [manager.monitoredRegions containsObject:region]) {
        NSString *message = [NSString stringWithFormat:@"%@ %@",
            region, error.localizedDescription];
        NSLog(@"%@",message);
    }
}

///通知代理正在监视新区域
- (void)locationManager:(CLLocationManager *)manager
didStartMonitoringForRegion:(CLRegion *)region API_AVAILABLE(ios(5.0), macos(10.8)) API_UNAVAILABLE(watchos, tvOS){
    
    //CLCircularRegion类定义圆形地理区域的位置和边界
    CLCircularRegion  *circularRegion = (CLCircularRegion *)region;
    CLLocation *location = [[CLLocation alloc]initWithLatitude:circularRegion.center.latitude longitude:circularRegion.center.longitude];
    
    //初始化地理编码器对象
    CLGeocoder *geocoder = [[CLGeocoder alloc]init];
    //提交指定位置的反向地理编码请求
    [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {
        
        if (placemarks.count >0) {

                CLPlacemark *placeMark = placemarks[0];
                NSLog(@"当前国家 - %@",placeMark.country);//当前国家
                NSLog(@"当前城市 - %@",placeMark.locality);//当前城市
                NSLog(@"当前位置 - %@",placeMark.subLocality);//当前位置
                NSLog(@"当前街道 - %@",placeMark.thoroughfare);//当前街道
                NSLog(@"具体地址 - %@",placeMark.name);//具体地址
                NSString *errorGeocoder = [NSString stringWithFormat:@"%@%@%@,%@",IS_NOT_EMPTY(placeMark.locality)? placeMark.locality : @"",IS_NOT_EMPTY(placeMark.subLocality)? placeMark.subLocality : @"" ,IS_NOT_EMPTY(placeMark.thoroughfare)? placeMark.thoroughfare : @"",IS_NOT_EMPTY(placeMark.name)? placeMark.name : @""];
            
                self.monitoringLabel.text = [NSString stringWithFormat:@"在监视新区域:%@",errorGeocoder];

            }else if(error == nil && placemarks.count){

                NSLog(@"无位置和错误返回");

            }else if(error){

                NSLog(@"loction error:%@",error);

            }

        }];
}

///通知代理位置更新已暂停
- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager API_AVAILABLE(ios(6.0)) API_UNAVAILABLE(watchos, tvos, macOS){
    self.locationUpdateLabel.text = @"位置更新已暂停";
}

///通知代理位置更新的传递已恢复
- (void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager API_AVAILABLE(ios(6.0)) API_UNAVAILABLE(watchos, tvos, macOS){
    self.locationUpdateLabel.text = @"位置更新的传递已恢复";
}

///通知代理不再延迟更新
- (void)locationManager:(CLLocationManager *)manager
didFinishDeferredUpdatesWithError:(nullable NSError *)error API_AVAILABLE(ios(6.0), macos(10.9)) API_UNAVAILABLE(watchos, tvOS){
    self.locationUpdateLabel.text = @"不再延迟更新";
}

///通知代理一个或多个信标在范围内。
- (void)locationManager:(CLLocationManager *)manager
didRangeBeacons:(NSArray *)beacons
               inRegion:(CLBeaconRegion *)region{
    
}

///通知代理检测到满足约束的信标。
- (void)locationManager:(CLLocationManager *)manager
        didRangeBeacons:(NSArray *)beacons
   satisfyingConstraint:(CLBeaconIdentityConstraint *)beaconConstraint API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, tvos, macOS){
    
}

///通知代理未检测到满足约束的信标。
- (void)locationManager:(CLLocationManager *)manager
didFailRangingBeaconsForConstraint:(CLBeaconIdentityConstraint *)beaconConstraint
                  error:(NSError *)error API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, tvos, macOS){
    
}

///通知代理收到了新的与访问相关的事件
- (void)locationManager:(CLLocationManager *)manager didVisit:(CLVisit *)visit API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(watchos, tvos, macOS){
    
}
@end

样式如图 :

IMG_1317.PNG

你可能感兴趣的:(Objective-C的CLLocation学习笔记)