欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

位置信息CLLocationManager

程序员文章站 2022-04-03 12:29:45
...

我们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];
}