位置信息CLLocationManager

我们app时常会需要使用用户的位置信息。可能是需要实时访问,也可能是在某个地方访问一次。CLLocationManager是位置访问的iOS 位置管理类,为了方便灵活的使用,我进行了简单的封装,可能需要在使用的时候,根据业务需求简单修改。


#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>

static NSNotificationName kLocationDidNotAuthNotification = @"kLocationDidNotAuthNotification";
static NSNotificationName kCityDidChangedNotification = @"kCityDidChangedNotification";//您所在的城市位置发生了改变

@class LocationManager;
@protocol LocationManagerDelegate<NSObject>
- (void)locationManagerDidUpdateLocation:(LocationManager *)manager;
- (void)locationManager:(LocationManager *)manager updateLocationError:(NSError *)error;
@end

@interface LocationManager : NSObject
@property (nonatomic, strong, readonly) CLPlacemark *livePlacemark;//实时的位置
@property (nonatomic, strong) CLPlacemark *currentPlacemark;//当前程序显示的位置
@property (nonatomic, weak) id<LocationManagerDelegate> delegate;
@property (nonatomic, assign) BOOL needLiveUpdate;//需要实时更新, 不会停止,大概十多秒就会更新一次。
@property (nonatomic, assign) CLLocationDistance distanceFilter;//实时更新情况下,移动次距离才会更新位置。默认1km
@property (nonatomic, assign) BOOL allowsBackgroundLocationUpdates;//设置后台更新,需要开启后台定位

+ (instancetype)shareInstance;
+ (BOOL)locationServicesEnabled;//判断是否开启位置权限
- (void)startUpdatingLocation; //needLiveUpdate提前设置
- (void)getLivePlacemark:(void(^)(CLPlacemark *placemark))getPlacemarkBlock errorBlock:(void(^)(NSError *error))errorBlock;//获取实时位置
- (void)showOpenAuthAlert;
@end


#import "LocationManager.h"
#import <UIKit/UIKit.h>

@interface LocationManager()<CLLocationManagerDelegate>
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) CLGeocoder *geocoder;// 地理编码器
@property (nonatomic, strong) CLPlacemark *livePlacemark;
@property (nonatomic, copy) void(^getPlacemarkBlock)(CLPlacemark *placemark);
@property (nonatomic, copy) void(^errorBlock)(NSError *error);
@property (nonatomic, assign) BOOL needAlertCityChanged;
@end

@implementation LocationManager
{
    CLPlacemark *_currentPlacemark;
}

+ (instancetype)shareInstance {
    static LocationManager *_shareInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _shareInstance = [[self alloc] init];
    });
    return _shareInstance;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
        _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        _locationManager.distanceFilter = 1000;
        
        _geocoder = [[CLGeocoder alloc] init];
        _needLiveUpdate = NO;
        _needAlertCityChanged = YES;
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
    }
    return self;
}

- (void)setAllowsBackgroundLocationUpdates:(BOOL)allowsBackgroundLocationUpdates {
    _allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates;
    
    _locationManager.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates;
    _locationManager.pausesLocationUpdatesAutomatically = !allowsBackgroundLocationUpdates;//设置是否允许系统自动暂停定位,这里要设置为NO
}

- (void)setDistanceFilter:(CLLocationDistance)distanceFilter {
    _distanceFilter = distanceFilter;
    _locationManager.distanceFilter = distanceFilter;
}

- (void)setCurrentPlacemark:(CLPlacemark *)currentPlacemark {
    _currentPlacemark = currentPlacemark;
    [NSKeyedArchiver archiveRootObject:currentPlacemark toFile:[LocationManager placemarkArchiverFile]];
}

- (CLPlacemark *)currentPlacemark {
    if (_currentPlacemark) {
        return _currentPlacemark;
    }else {
        return [NSKeyedUnarchiver unarchiveObjectWithFile:[LocationManager placemarkArchiverFile]];
    }
}

- (void)requestLocationServicesAuthorization {
    
    if (_needLiveUpdate) {
        [_locationManager requestAlwaysAuthorization];
    }else {
        [_locationManager requestWhenInUseAuthorization];
    }
}

- (void)getLivePlacemark:(void (^)(CLPlacemark *))getPlacemarkBlock errorBlock:(void (^)(NSError *))errorBlock {
    _getPlacemarkBlock = getPlacemarkBlock;
    _errorBlock = errorBlock;
    
    if (self.livePlacemark && self.needLiveUpdate) {
        if (self.getPlacemarkBlock) {
            self.getPlacemarkBlock(self.livePlacemark);
            self.getPlacemarkBlock = nil;
        }
    }else {
        [self startUpdatingLocation];//重复调用没有关系
    }
}

//是否开启了位置服务
+ (BOOL)locationServicesEnabled {
    return CLLocationManager.locationServicesEnabled;
}

- (void)startUpdatingLocation {
    [self requestLocationServicesAuthorization];
    [_locationManager startUpdatingLocation];
}

- (void)stopUpdatingLocation {
    if (!_needLiveUpdate) {
        [_locationManager stopUpdatingLocation];
    }
}


#pragma mark - delegate

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
    if (locations.count < 1) return;
    CLLocation *locaiton = locations.lastObject;
    if (locaiton == nil) {
        return;
    }
    [self analyzeLocation:locaiton];
    
    [_geocoder reverseGeocodeLocation:locaiton completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
        if (placemarks.count < 1) return;
        CLPlacemark *placemark = placemarks.lastObject;
        if (placemark == nil) {
            return;
        }
        _livePlacemark = placemark;
        [self analyzePlacemark:placemark];
        [self stopUpdatingLocation];
        if (self.delegate && [self.delegate respondsToSelector:@selector(locationManagerDidUpdateLocation:)]) {
            [self.delegate locationManagerDidUpdateLocation:self];
        }
        if (self.getPlacemarkBlock) {
            self.getPlacemarkBlock(placemark);
            self.getPlacemarkBlock = nil;
        }
        
        [self checkLiveCityIsChanged];
    }];
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
    if (error.code == kCLErrorDenied) {
        [self stopUpdatingLocation];
        if (self.delegate && [self.delegate respondsToSelector:@selector(locationManager:updateLocationError:)]) {
            [self.delegate locationManager:self updateLocationError:error];
        }
        if (self.errorBlock) {
            self.errorBlock(error);
            self.errorBlock = nil;
        }
    }
}

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    
    if (self.needLiveUpdate) {
        if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorizedAlways) {
            [self startUpdatingLocation];
        }
    }
    
    [[NSNotificationCenter defaultCenter] postNotificationName:kLocationDidNotAuthNotification object:nil];
}

- (void)showOpenAuthAlert {
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"打开定位开关" message:@"定位服务为开启,请进入系统【设置】>【隐私】>【定位服务】中打开开关,并允许xxapp使用定位服务" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *iKnow = [UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [alert dismissViewControllerAnimated:YES completion:nil];
    }];
    [alert addAction:iKnow];
    [[UIApplication sharedApplication].delegate.window.rootViewController presentViewController:alert animated:YES completion:nil];
}

- (void)uploadLocationToServer {
    //self.placemark - > server
    //notification.post
}

// 判断实时城市是否发生改变
- (void)checkLiveCityIsChanged {
    if (self.currentPlacemark && ![self.livePlacemark.locality isEqualToString:self.currentPlacemark.locality] && self.needAlertCityChanged) {
        [[NSNotificationCenter defaultCenter] postNotificationName:kCityDidChangedNotification object:nil];
        self.needAlertCityChanged = NO;
    }
}

+ (NSString *)placemarkArchiverFile {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDir = [paths objectAtIndex:0];
    NSString *dstPath = [documentDir stringByAppendingPathComponent:@"placemark.archiver"];
    
    return dstPath;
    
}

#pragma mark - other

- (void)applicationDidEnterBackgroundNotification {
    self.needAlertCityChanged = YES;
}

- (void)analyzeLocation:(CLLocation *)location {
    
//    CLLocationDegrees lat = locaiton.coordinate.latitude;//纬度
//    CLLocationDegrees lon = locaiton.coordinate.longitude;//经度
//    CLLocationDistance altitude = locaiton.altitude; //海拔
//    CLLocationDirection course = locaiton.course;//航向
//    CLLocationSpeed speed = locaiton.speed;//速度
}

- (void)analyzePlacemark:(CLPlacemark *)placemark {
    
//    CLLocation *placemark_location = placemark.location;
//    CLRegion *region = placemark.region;//区域,可以用来区域检测
//    NSString *name = placemark.name;
//    NSString *thoroughfare = placemark.thoroughfare;//街道
//    NSString *subThoroughfare = placemark.subThoroughfare;//子街道
//
//    NSString *city = placemark.locality;
//    if (!city) {
//        city = placemark.administrativeArea;//直辖市
//    }
//    NSString *subLocality = placemark.subLocality;//区
//    NSString *country = placemark.country;//国家
//
}


/**
 最近项目中对于经纬度的反地理编码发现几个坑:
 1.通过系统定位didUpdateLocations方法得到的经纬度,不区分国内国外都是地球坐标(世界标准地理坐标(WGS-84))
 如果用户通过点击地图,(CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView*)view;方法转换后获得的经纬度,国内的到的是火星坐标(中国国测局地理坐标(GCJ-02)),国外是地球坐标。
 2.reverseGeocodeLocation的坑:在iOS9.XXX中,这个方法需要传入的经纬度必须为地球坐标,而在iOS9之前和iOS10中,这个方法传入的经纬度必须为火星坐标。
 3.地图大头针的MKPointAnnotation设置的经纬度必须为火星坐标,不然会出现偏移
 */

@end

获取位置信息包括:实时位置信息,需要时再获取位置信息
默认是需要时再获取位置信息,如需实时获取位置信息,需配置:

[LocationManager shareInstance].distanceFilter = kCLDistanceFilterNone;// 根据项目需求,自己设置
[LocationManager shareInstance].needLiveUpdate = YES;

获取位置信息的方式有两种:delegate 和 block

@protocol LocationManagerDelegate<NSObject>
- (void)locationManagerDidUpdateLocation:(LocationManager *)manager;
- (void)locationManager:(LocationManager *)manager updateLocationError:(NSError *)error;
@end

if ([LocationManager locationServicesEnabled]) {
   [[LocationManager shareInstance] startUpdatingLocation];
}else {
  [[LocationManager shareInstance] showOpenAuthAlert];
  }
if ([LocationManager locationServicesEnabled]) {
    [[LocationManager shareInstance] getLivePlacemark:^(CLPlacemark *placemark) {

    } errorBlock:^(NSError *error) {

    }];
}else {
    [[LocationManager shareInstance] showOpenAuthAlert];
}

监听城市改变

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cityChanged) name:kCityDidChangedNotification object:nil];


- (void)switchCityAlert{
    CLPlacemark * livePlacemark = [LocationManager shareInstance].livePlacemark;
    NSString *message = [NSString stringWithFormat:@"定位显示你在%@, 是否切换当前城市至%@", livePlacemark.locality, livePlacemark.locality];
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"切换城市" message:message preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [alert dismissViewControllerAnimated:YES completion:nil];
    }];
    [alert addAction:cancel];
    __weak __typeof(&*self)weakSelf = self;
    UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [LocationManager shareInstance].currentPlacemark = livePlacemark;
        weakSelf.currentLocationLabel.text = livePlacemark.locality;
        [alert dismissViewControllerAnimated:YES completion:nil];
    }];
    [alert addAction:sure];
    [[UIApplication sharedApplication].delegate.window.rootViewController presentViewController:alert animated:YES completion:nil];
}

- (void)cityChanged {
    [self switchCityAlert];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 用到的组件 1、通过CocoaPods安装 2、第三方类库安装 3、第三方服务 友盟社会化分享组件 友盟用户反馈 ...
    SunnyLeong阅读 14,613评论 1 180
  • 繁星与梦 跳入井里 乌龟跟马 爬的一样快 嘴角同眉 往上扬 七月结尾 下场雪
    不携剑的侠阅读 202评论 1 1
  • 今天结束了
    最傻的负能阅读 171评论 0 0
  • 在我们漫长的一生中,有的人注定只是我们生命中的过客,他们陪伴我们走过一段或长或短的旅程,然后分道扬镳。 而年少时的...
    柒燃阅读 519评论 0 0
  • 思绪万千,不知道从何说起。就在昨天,我和我的女友告别了五年的恋爱生涯,尝到了初恋痛苦的滋味。我还是从头说起吧! ...
    嘻哈呦呦阅读 364评论 1 2