github地址:https://github.com/longitachi/GPSLocationPicker
博客资源下载地址:http://download.csdn.net/detail/longitachi/9174445
本文所使用的定位为iOS系统原生定位,使用前需导入 CoreLocation.framework框架
现在越来越多的项目中都会或多或少的使用到定位功能,而有些时候并不是拿到了用户的当前坐标就可以了,而是需要对采集到的坐标进行一个 有效精度,和有效距离的判断。比如某些企业级软件应用,用户进行考勤时候,则需要对精度和有效距离做一个较为精确的控制。
针对这些需求,进行设计,主要设计思想是将定位类分为两大块,分别为 GPSLocationPicker和GPSValidLocaitionPicker。
GPSLocationPicker主要负责底层的定位,只满足用户对有效精度的限制,无等待对话框显示;
GPSValidLocaitionPicker 则负责对用户额外属性的扩展,如定位超时时长、有效精度、有效距离等,且该类中有等待对话框,并可以根据用户设置的是否显示GPS详情进行显示。
不显示定位详细信息的效果图
显示定位详细信息的效果图
下面先说下GPSLocationPicker:
该类的.h文件中主要提供以下参数及接口
#define kZeroLocation [[CLLocation alloc] initWithLatitude:0 longitude:0]
#define kLocationFailedError [NSError errorWithDomain:@"location failed" code:-100 userInfo:nil]
typedef void (^LocationResult)(CLLocation *location, NSError *error);
@interface GPSLocationPicker : NSObject
//定位期望精度(单位:m),默认为-1,不要求采集精度,则拿到坐标直接回调
@property (nonatomic, assign) CLLocationAccuracy precision;
+ (instancetype)shareGPSLocationPicker;
/**
* @brief 启动定位,并设置定位成功回调
*/
- (void)startLocationAndCompletion:(LocationResult)completion;
/**
* @brief 停止定位
*/
- (void)stop;
@end
.m中对应的实现:
#import "GPSLocationPicker.h"
@interface GPSLocationPicker () <CLLocationManagerDelegate>
{
CLLocationManager *_locationManager;
LocationResult _locationResultBlock;
BOOL _isStop;
}
@end
@implementation GPSLocationPicker
static GPSLocationPicker *picker = nil;
+ (instancetype)shareGPSLocationPicker
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
picker = [[GPSLocationPicker alloc] init];
});
return picker;
}
+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ //onceToken是GCD用来记录是否执行过 ,如果已经执行过就不再执行(保证执行一次)
picker = [super allocWithZone:zone];
});
return picker;
}
- (instancetype)init
{
self = [super init];
if (self) {
_precision = -1;
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
switch (status) {
case kCLAuthorizationStatusNotDetermined:
if ([[[UIDevice currentDevice] systemVersion] doubleValue] > 8.0) {
[_locationManager requestWhenInUseAuthorization];
} else {
[_locationManager startUpdatingLocation];
}
break;
case kCLAuthorizationStatusDenied:
//请打开系统设置中\"隐私->定位服务\",允许使用您的位置。
break;
case kCLAuthorizationStatusRestricted:
//定位服务无法使用!
break;
default:
[_locationManager startUpdatingLocation];//开启位置更新
break;
}
}
#pragma mark - 启动定位
- (void)startLocationAndCompletion:(LocationResult)completion
{
//初始化CLLocationManager
if (_locationManager) {
_isStop = NO;
_locationManager = nil;
}
if (_locationResultBlock) {
_locationResultBlock = nil;
}
NSLog(@"启动定位");
_locationResultBlock = completion;
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
_locationManager.distanceFilter = kCLDistanceFilterNone;
if ([[[UIDevice currentDevice] systemVersion] doubleValue] > 8.0)
{
[_locationManager requestAlwaysAuthorization];// 前后台同时定位
}
[_locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
if (_isStop) {
return;
}
//得到Location
CLLocation *coord = [locations objectAtIndex:0];
NSLog(@"采集到的坐标经度:%f, 维度:%f 精度%f", coord.coordinate.longitude, coord.coordinate.latitude, coord.horizontalAccuracy);
//判断采集到的精度
if (_locationResultBlock && !_isStop && (self.precision == -1 || coord.horizontalAccuracy <= self.precision)) {
_locationResultBlock(coord, nil);
}
}
#pragma mark - 定位失败
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(@"定位失败:%@", error);
if (!_isStop && _precision == -1 && _locationResultBlock) {
_locationResultBlock(kZeroLocation, error);
}
}
- (void)stop
{
_isStop = YES;
[_locationManager stopUpdatingLocation];
}
以上则为对 GPSLocationPicker 的全部实现,用户如果不需要对有效距离进行限制则可以直接调用该类,调用方法为
//设置有效精度
[GPSLocationPickershareGPSLocationPicker].precision = -1;
[[GPSLocationPickershareGPSLocationPicker]startLocationAndCompletion:^(CLLocation *location,NSError *error) {
// your code here
}];
在介绍下GPSValidLocaitionPicker的实现:
该类的.h文件中主要提供以下参数及接口
<span style="font-size:12px;">#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <UIKit/UIKit.h>
#define kIsShowGPSDetailInfo @"isShowDetailInfo"
typedef void (^ValidLocationResult)(CLLocation *location, NSError *error);
@interface GPSValidLocationPicker : NSObject
//定位超时时间(单位:s), 默认为-100
@property (nonatomic, assign) int timeoutPeriod;
//定位期望精度(单位:m),默认为-100
@property (nonatomic, assign) CLLocationAccuracy precision;
//当前坐标
@property (nonatomic, assign) CLLocationCoordinate2D nowCoordinate;
//定位有效距离(单位:m),默认为-100
@property (nonatomic, assign) CLLocationDistance validDistance;
+ (instancetype)shareGPSValidLocationPicker;
/**
* @brief 启动定位,如果需要对超时时长、采集到的坐标精度和有效距离做要求, 则需要在调用该方法之前对 timeoutPeriod, precision, nowCoordinate, valiDistance 做一个对应的赋值
*/
- (void)startLocationAndCompletion:(ValidLocationResult)completion;
/**
* @brief 重置变量为默认值
*/
- (void)resetDefaultVariable;
@end</span><u style="font-size:18px;">
</u>
.m中对应实现
#import "GPSValidLocationPicker.h"
#import "GPSLocationPicker.h"
#import "MBProgressHUD.h"
#import "MBProgressHUD+DetailLabelAlignment.h"
#define kDefaultValue -100
@interface GPSValidLocationPicker () <CLLocationManagerDelegate, MBProgressHUDDelegate>
{
CLLocationAccuracy _nowPrecision;//定位拿到的精度
CLLocationDistance _collectDistance;//当前采集到的点与用户传进来的点的距离
NSTimer *_timer;
MBProgressHUD *_waitView;
int _totalTime;
ValidLocationResult _locationResultBlock;
}
@end
@implementation GPSValidLocationPicker
static GPSValidLocationPicker *_ValidLocationPicker = nil;
+ (instancetype)shareGPSValidLocationPicker
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_ValidLocationPicker = [[self alloc] init];
});
return _ValidLocationPicker;
}
+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_ValidLocationPicker = [super allocWithZone:zone];
});
return _ValidLocationPicker;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self resetDefaultVariable];
}
return self;
}
- (void)resetDefaultVariable
{
_nowPrecision = _timeoutPeriod = _precision = _validDistance = _collectDistance = kDefaultValue;
}
#pragma mark - 启动定位
- (void)startLocationAndCompletion:(ValidLocationResult)completion
{
if (_locationResultBlock) {
_locationResultBlock = nil;
}
_locationResultBlock = completion;
_totalTime = self.timeoutPeriod;
//显示等待视图
[self beginWaiting:@"定位中,请稍后。。。" mode:_totalTime>0?MBProgressHUDModeDeterminateHorizontalBar:MBProgressHUDModeIndeterminate];
if (_timeoutPeriod > 0) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateProgress) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
[self startGetLocation];
}
- (void)startGetLocation
{
//这里设置为-1,使GPSLocationPicker拿到坐标后直接回调
[GPSLocationPicker shareGPSLocationPicker].precision = -1;
[[GPSLocationPicker shareGPSLocationPicker] startLocationAndCompletion:^(CLLocation *location, NSError *error) {
[self judgeNowLocationIsValid:location];
}];
}
#pragma mark - 判断当前采集到的坐标是否符合标准
- (void)judgeNowLocationIsValid:(CLLocation *)pickCoord
{
_nowPrecision = pickCoord.horizontalAccuracy;
//首先判断坐标是否有效
if ( pickCoord.coordinate.latitude == 0 || pickCoord.coordinate.longitude == 0) {
return;
}
if (_precision == kDefaultValue && _validDistance == kDefaultValue) {
//没有精度和有效距离的限制,当前坐标有效
[self locationSuccess:pickCoord];
return;
}
BOOL coordIsValid = YES;
if (_precision != kDefaultValue && _nowPrecision >= _precision) {
coordIsValid = NO;
}
if (_validDistance != kDefaultValue
&& _nowCoordinate.latitude != 0
&& _nowCoordinate.longitude != 0
&& [self coordIsValid:pickCoord] == NO) {
coordIsValid = NO;
}
//如果坐标符合期望精度及有效距离
if (coordIsValid) {
NSLog(@"符合了标准:%d", coordIsValid);
[self locationSuccess:pickCoord];
}
}
#pragma mark - 显示更新定位进度
- (void)updateProgress
{
if (_timeoutPeriod == -1) {
//定位超时
[self locationTimeOut:[NSError errorWithDomain:@"location failed" code:-1 userInfo:nil]];
return;
}
if (!_waitView) {
[self beginWaiting:@"定位中,请稍后。。。" mode:MBProgressHUDModeDeterminateHorizontalBar];
}
_timeoutPeriod--;
_waitView.detailsLabelText = [self getGPSDetailInfo];
if (_totalTime > 0) {
_waitView.progress = (float)(_totalTime-_timeoutPeriod)/_totalTime;
}
}
#pragma mark - 拿到符合标准的坐标
- (void)locationSuccess:(CLLocation *)coord
{
NSLog(@"%s", __FUNCTION__);
[_timer invalidate];
[_waitView hide:YES];
[[GPSLocationPicker shareGPSLocationPicker] stop];
if (_locationResultBlock) {
_locationResultBlock(coord, nil);
}
}
#pragma mark - 定位超时
- (void)locationTimeOut:(NSError *)error
{
NSLog(@"%s", __FUNCTION__);
[_timer invalidate];
[[GPSLocationPicker shareGPSLocationPicker] stop];
[_waitView hide:YES];
if (_locationResultBlock) {
_locationResultBlock(kZeroLocation, kLocationFailedError);
}
}
- (NSString *)getGPSDetailInfo
{
//是否显示gps详情 (根据用户需要进行设置)
BOOL isShowGPSDetail = [[NSUserDefaults standardUserDefaults] boolForKey:kIsShowGPSDetailInfo];
NSMutableString *detailStr = [NSMutableString string];
if (_timeoutPeriod != kDefaultValue) {
if (_timeoutPeriod >= 0) {
[detailStr appendString:[NSString stringWithFormat:@"等待时间:%d", _timeoutPeriod]];
} else {
[detailStr appendString:@"定位超时"];
}
}
if (_precision != kDefaultValue && isShowGPSDetail) {
[detailStr appendString:[NSString stringWithFormat:@"\n标准精度:%.0f米", self.precision]];
if (_nowPrecision != kDefaultValue) {
[detailStr appendString:[NSString stringWithFormat:@"\n当前精度:%.0f米", _nowPrecision]];
}
}
if (_validDistance != kDefaultValue && isShowGPSDetail) {
[detailStr appendString:[NSString stringWithFormat:@"\n标准距离:%.0f米", self.validDistance]];
if (_collectDistance != kDefaultValue) {
[detailStr appendString:[NSString stringWithFormat:@"\n当前距离:%.0f米", _collectDistance]];
} else {
[detailStr appendFormat:@"\n当前距离∞米"];
}
}
return detailStr.length == 0 ? @"" : detailStr;
}
#pragma mark - 计算采集到的坐标与传入坐标距离是否符合标准
- (BOOL)coordIsValid:(CLLocation *)nowLocation
{
if (nowLocation.coordinate.latitude == 0 || nowLocation.coordinate.longitude == 0) {
return NO;
}
CLLocation *lastLocation = [[CLLocation alloc] initWithLatitude:self.nowCoordinate.latitude longitude:self.nowCoordinate.longitude];
CLLocationDistance distance = [nowLocation distanceFromLocation:lastLocation];
_collectDistance = distance;
NSLog(@"距离:%f", distance);
if (distance <= _validDistance) {
return YES;
} else {
return NO;
}
}
#pragma mark - 显示等待视图
-(void)beginWaiting:(NSString *)message mode:(MBProgressHUDMode)mode
{
if (!_waitView) {
_waitView = [[MBProgressHUD alloc] initWithView:[UIApplication sharedApplication].keyWindow];
[[UIApplication sharedApplication].keyWindow addSubview:_waitView];
_waitView.delegate = self;
_waitView.square = NO;
_waitView.mode = mode;
[_waitView setDetailLabelAlignment:NSTextAlignmentLeft];
}
_waitView.labelText = message;
_waitView.detailsLabelText = [self getGPSDetailInfo];
[_waitView show:YES];
}
- (void)hudWasHidden:(MBProgressHUD *)hud
{
[hud removeFromSuperview];
_waitView = nil;
}
@end
GPSValidLocaitionPicker类中使用到了第三方库MBProgressHUD, 为了显示定位详情时的信息左对齐又不影响原三方库,我为其添加了类别 MBProgressHUD+DetailLabelAlignment.h,实现非常简单,不过唯一需要注意的一点就是 MBProgressHUD原有的detailsLabel在.m文件中,外界无法调用,我把它移动到了.h中
//这里为了方便设置MBProgressHUD的detailLabel的对齐方式,把MBProgressHUD类中的detailsLabel属性从.m中移动到了.h中
@interface MBProgressHUD (DetailLabelAlignment)
/**
* @brief 设置详情label对齐方式
*/
- (void)setDetailLabelAlignment:(NSTextAlignment)alignment;
@end
#import "MBProgressHUD+DetailLabelAlignment.h"
@implementation MBProgressHUD (DetailLabelAlignment)
- (void)setDetailLabelAlignment:(NSTextAlignment)alignment
{
self->detailsLabel.textAlignment = alignment;
}
@end
到这里整个 GPSValidLocaitionPicker 便实现完成,调用方法为
GPSValidLocationPicker *gpsPicker = [GPSValidLocationPickershareGPSValidLocationPicker];
//因为该类设计为单例模式,所以如果多处用则可能出现有些地方设置了变量值保留的问题,所以尽量在调用定位前进行一次重置
[gpsPicker resetDefaultVariable];
//测试用值
gpsPicker.timeoutPeriod =20;
gpsPicker.precision =10;
gpsPicker.validDistance =100;
CLLocationCoordinate2D coord =CLLocationCoordinate2DMake(31.13,121.33);
gpsPicker.nowCoordinate = coord;
[gpsPicker startLocationAndCompletion:^(CLLocation *location,NSError *error) {
if (error) {
_textView.text = [NSStringstringWithFormat:@"未采集到符合精度的坐标,错误信息:%@", error];
NSLog(@"未采集到符合精度的坐标,错误信息:%@", error);
} else {
_textView.text = [NSStringstringWithFormat:@"采集到符合精度的坐标经度%f,维度%f", location.coordinate.longitude, location.coordinate.latitude];
NSLog(@"采集到符合精度的坐标经度%f,维度%f", location.coordinate.longitude, location.coordinate.latitude);
}
}];