前言
现在很多app都是根据用户所在行政区域才展示数据,当然我也碰到了不少这样项目。现在我把自己对CLLocationManager封装的抽离出来了,如果你项目中对LocationManager有以下几个特点,那么可以直接拿去用。
1. 只需要成功定位一次;
2. 只需要拿到坐标或者行政区域;
3. 权限改变后,有对应的提示;
话不多说,直接上码。
思路
整个项目中,基本上很少用delegate,一般都是使用block,如果不习惯的可以自行替换。整体的封装思路如下:
1. 创建单例;
2. 创建3个`block`:
* `PRLocationCurrentAuthStatus`:当前权限;
* `PRLocationSuccess`:成功回调
* `PRLocationFaile`:失败回调
3. 调用定位前判断是否有权限;
4. 回调回来的坐标通过`CLGeocoder`的`- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;`方法进行反地理编码
代码
首先创建一个Subclass of是CLLocationManager的PRLocationManager的类,在.h里面
typedef void (^PRLocationCurrentAuthStatus)(BOOL isEnableLocation);
typedef void (^PRLocationSuccess)(CLLocation *location, CLPlacemark *placemark);
typedef void (^PRLocationFaile)(NSError *error);
@interface PRLocationManager : CLLocationManager
@property (nonatomic, copy) PRLocationCurrentAuthStatus currentAuthStatus;
/**
单例
**@return** 单例
*/
+ (PRLocationManager *)shareManager;
/**
判断能否定位
**@return** YES 可以定位 NO 不能定位
*/
- (BOOL)isEnabledLocation;
/**
开始定位
*/
- (void)startLocation;
/**
停止定位,开始后要手动停止
*/
- (void)stopLocation;
/**
回调结果
**@param** success 成功回调
**@param** faile 失败回调
*/
- (void)locationStartSuccess:(PRLocationSuccess)success faile:(PRLocationFaile)faile;
/**
打开设置
*/
+ (void)openLocationService;
在.m里面
@interface PRLocationManager () <CLLocationManagerDelegate>
@property (nonatomic, copy) PRLocationSuccess successBlock;
@property (nonatomic, copy) PRLocationFaile errorBlock;
@end
@implementation PRLocationManager
#pragma mark ^^^^^^ 单例 ^^^^^^
+ (PRLocationManager *)shareManager {
static PRLocationManager *shareObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareObject = [[PRLocationManager alloc] init];
});
return shareObject;
}
#pragma mark ^^^^^^ Init ^^^^^^
- (instancetype)init {
if ([super init]) {
self.delegate = self;
// 设置位置精确度 最佳精确度
self.desiredAccuracy = kCLLocationAccuracyBest;
// 设置位置距离精确度
self.distanceFilter = 10.0;
// 始终允许访问位置信息
if ([self respondsToSelector:@selector(requestAlwaysAuthorization)]) {
[self requestAlwaysAuthorization];
}
// 使用应用程序期间允许访问位置数据
if ([self respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
[self requestWhenInUseAuthorization];
}
}
return self;
}
#pragma mark ^^^^^^ Method ^^^^^^
// 判断定位操作是否被允许
- (BOOL)isEnabledLocation {
if ([CLLocationManager locationServicesEnabled]) {
return YES;
}
return NO;
}
// 定位开始
- (void)startLocation {
if ([self isEnabledLocation]) {
NSLog(@"开始定位");
[self startUpdatingLocation];
}
}
// 开始定位
- (void)locationStartSuccess:(PRLocationSuccess)success faile:(PRLocationFaile)faile {
self.successBlock = [success copy];
self.errorBlock = [faile copy];
[self startLocation];
}
// 定位停止
- (void)stopLocation {
NSLog(@"结束定位");
[self stopUpdatingLocation];
}
#pragma mark - CLLocationManagerDelegate
#pragma mark ^^^^^^ 开始定位 ^^^^^^
// 获取定位信息
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
// 获取最新的位置
CLLocation *currentLocation = [locations lastObject];
// 纬度:latitude 经度:longitude
NSLog(@"纬度=%f,经度=%f", currentLocation.coordinate.latitude, currentLocation.coordinate.longitude);
// 初始化反地理编码 geocoder
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
// 根据经纬度反向地理编译出地址信息
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *array, NSError *error) {
if (array.count > 0) {
CLPlacemark *placemark = [array objectAtIndex:0];
!self.successBlock ?: self.successBlock(currentLocation, placemark);
} else {
if (([error code] == kCLErrorDenied)) {
!self.currentAuthStatus ? (!self.errorBlock ?: self.errorBlock(error)) : self.currentAuthStatus(NO); // 如果self.currentAuthStatus没有回调,那么就回调self.errorBlock
} else {
!self.errorBlock ?: self.errorBlock(error);
}
}
}];
// 如果只需要获取一次,那么手动调用停止
// 如果想持续获取,那么注释掉此行代码
[self stopLocation];
}
#pragma mark ^^^^^^ 定位失败 ^^^^^^
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
if (([error code] == kCLErrorDenied)) {
!self.currentAuthStatus ? (!self.errorBlock ?: self.errorBlock(error)) : self.currentAuthStatus(NO); // 如果self.currentAuthStatus没有回调,那么就回调self.errorBlock
} else {
!self.errorBlock ?: self.errorBlock(error);
}
[self stopLocation];
}
#pragma mark ^^^^^^ 授权改变 ^^^^^^
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
status == kCLAuthorizationStatusNotDetermined ||
status == kCLAuthorizationStatusAuthorizedAlways) {
!self.currentAuthStatus ?: self.currentAuthStatus(YES); // 可以定位
} else {
!self.currentAuthStatus ?: self.currentAuthStatus(NO); // 不能定位
}
}
#pragma mark ^^^^^^ 打开设置 ^^^^^^
+ (void)openLocationService {
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
if (@available(iOS 10.0, *)) {
[[UIApplication sharedApplication] openURL:url options:@{UIApplicationOpenURLOptionUniversalLinksOnly:@""} completionHandler:nil];
} else{
[[UIApplication sharedApplication] openURL:url];
}
}
}
调用
self.resultLabel.text = @"定位中...";
[[PRLocationManager shareManager] locationStartSuccess:^(CLLocation *location, CLPlacemark *placemark) {
// 获取城市
NSString *city = placemark.locality;
if (!city) {
// 四大直辖市的城市信息无法通过locality获得,只能通过获取省份的方法来获得(如果city为空,则可知为直辖市)
city = placemark.administrativeArea;
}
NSLog(@"city:%@",city);
self.resultLabel.text = [NSString stringWithFormat:@"定位成功:%@",city];
} faile:^(NSError *error) {
if (error) {
if (error.code == 2) {
self.resultLabel.text = @"网络状态不好,请重试";
NSLog(@"网络状态不好,请重试");
} else {
self.resultLabel.text = [NSString stringWithFormat:@"查询错误 = %@", error];
}
} else {
self.resultLabel.text = @"查询失败,没有结果";
}
}];
[PRLocationManager shareManager].currentAuthStatus = ^(BOOL isEnableLocation) {
if (isEnableLocation) {
self.resultLabel.text = @"当前权限,可以定位";
} else {
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"定位失败" message:@"请前往设置,打开定位权限" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
//
}];
UIAlertAction *sureAction = [UIAlertAction actionWithTitle:@"前往" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
[PRLocationManager openLocationService];
}];
[alertVC addAction:cancelAction];
[alertVC addAction:sureAction];
[self presentViewController:alertVC animated:YES completion:nil];
self.resultLabel.text = @"当前权限,不能定位,请打开权限";
}
};