位置信息CLLocationManager
2018-04-29 本文已影响13人
xiari1991
我们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];
}