iOS小技能:封装定位SDK,统一数据模型和错误处理。

2022-08-22 11:45:00 浏览数 (1)

引言

需求背景:收款页面需要请求IP定位API获取经纬度,由于高德的API的库不准确(没有实时更新),使用公司决定换腾讯API。

本人推荐app侧的SDK定位使用高德,因为高德SDK定位更准确,错误信息也更详细。

app侧使用定位的相关功能:

  1. 商户进件APP:商户详情的重新定位,编辑进件信息时的商户地址定位、支付终端绑定的重新定位、新增拜访记录的定位、新增/编辑企业的企业地址定位
  2. 商户端APP:终端管理的设备定位、店铺定位。

I 单次定位

1.1 腾讯SDK(TencentLBS)

代码语言:javascript复制
/**
 * 设置用户是否同意隐私协议政策
 * <p>调用其他接口前必须首先调用此接口进行用户是否同意隐私政策的设置,传入YES后才能正常使用定位功能,否则TencentLBSLocationManager初始化不成功,返回nil,定位功能均无法使用</p>
 * @param isAgree 是否同意隐私政策
 */
  (void)setUserAgreePrivacy:(BOOL) isAgree;
/**
 *  单次定位
 *
 *  该方法为下面方法的一层封装。
 *  level默认是TencentLBSRequestLevelPoi
 *  timeout默认是10s
 */
- (BOOL)requestLocationWithCompletionBlock:(TencentLBSLocatingCompletionBlock)completionBlock;



// 先执行代理方法tencentLBSDidChangeAuthorization再执行此回调
/**
 * 当前属于模糊定位状态时,通过该接口请求暂时的完全定位精度的权限
 * @param purposeKey 需要在info.plist中配置NSLocationTemporaryUsageDescriptionDictionary key值和对应的申请该权限的描述理由
 * @param completion 在弹框让用户选择后的用户的反馈,如果用户授予该权限,block中的参数为nil,如果未授予,block中的参数将为PurposeKey对于的key的描述(如PurposeKey=TemporaryPurposKey_1)
 */
- (void)requestTemporaryFullAccuracyAuthorizationWithPurposeKey:(NSString *)purposeKey
                                                     completion:(void (^)(NSError *))completion;

初始化locationManager

代码语言:javascript复制
- (TencentLBSLocationManager *)locationManager{

    if(_locationManager == nil){
        _locationManager = [[TencentLBSLocationManager alloc] init];


        [self configLocationManager];


    }


    return _locationManager;


}

- (void)configLocationManager {
    [TencentLBSLocationManager setUserAgreePrivacy:YES];

  _locationManager = [[TencentLBSLocationManager alloc] init];

    [_locationManager setDelegate:self];
    [_locationManager setPausesLocationUpdatesAutomatically:NO];
//    [_locationManager setAllowsBackgroundLocationUpdates:YES];
    [_locationManager setApiKey:@"-----6JBM3"];//
//
    //若获取的drLocatin中带有地址信息,可
    [_locationManager setRequestLevel:TencentLBSRequestLevelAdminName];
    

//处理首次定位,原生API处理首次定位(推荐)QCTLocationServiceUtil isHasLocationAuthorityWithisShowAlert:YES block
    //不使用SDK的方法
//    CLAuthorizationStatus authorizationStatus= [CLLocationManager authorizationStatus];
//    if (authorizationStatus == kCLAuthorizationStatusNotDetermined) {
//        [self.locationManager requestWhenInUseAuthorization];
//    }
//
    
}


单次定位

代码语言:javascript复制
- (void)SingleLocation:(TencentLBSLocatingCompletionBlock)completionBlock{
    
    
__weak __typeof__(self) weakSelf = self;
    
    
    
//处理首次定位
[QCTLocationServiceUtil isHasLocationAuthorityWithisShowAlert:YES block:^(id  _Nonnull sender) {
    
    //模糊定位适配
    [weakSelf setuprequestLocationWithCompletionBlock:completionBlock];
    
    
}];
    
    
    

}

- (void)setuprequestLocationWithCompletionBlock:(TencentLBSLocatingCompletionBlock)completionBlock{
    
    
        
    self.block =completionBlock;// 用于适配iOS14
    
    
    [SVProgressHUD showWithStatus:@"定位中.."];
    
    //1.iOS 模糊定位适配
//    可以使用以下方法判断当前应用的定位精度权限,业务可根据相应的值做出不同的操作:
    if (@available(iOS 14.0, *)) {
        TencentLBSAccuracyAuthorization accAuthor = [TencentLBSLocationManager accuracyAuthorization];

        if(accAuthor == TencentLBSAccuracyAuthorizationReducedAccuracy){
            
    //        当前属于模糊定位状态时,通过该接口请求暂时的完全定位精度的权限

            //权限的变更会通过TencentLBSLocationManagerDelegate中的 - (void)tencentLBSDidChangeAuthorization:(TencentLBSLocationManager *)manager方法回调
            
                                self.isrequestTemporaryFullAccuracyAuthorizationWithPurposeKey = YES;
                    // 记录当前是否向用户申请临时开启一次精确位置权限,用于【如果定位精度权限变更为精确的时候,再次更新定位信息】

            [self.locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:(  @"purposeKey4changeInfo") completion:^(NSError * _Nonnull err) {
                
                
                // * @param completion 在弹框让用户选择后的用户的反馈,如果用户授予该权限,block中的参数为nil,如果未授予,block中的参数将为PurposeKey对于的key的描述(如PurposeKey=TemporaryPurposKey_1)
// 先执行代理方法tencentLBSDidChangeAuthorization再执行此回调
                if(!err){//未授予,则直接定位
                    
                    
                    [self requestLocation];

                    
                }

                
            }];

            return ;
        }
        

        
    }
    // 2. 调用单次定位
    
    [self requestLocation];

}

- (void)requestLocation{
//    [self configLocationManager ];
    
    
    [self.locationManager requestLocationWithCompletionBlock:
        ^(TencentLBSLocation *location, NSError *error) {
            NSLog(@"%@, %@, %@", location.location, location.name, location.address);
        [SVProgressHUD dismiss];

        if(self.block){
            
            
            self.block(location,error);
            
            
            
        }

        }];
    
    

    
}

/**
 *  定位权限状态改变时回调函数
 *  @param manager 定位 TencentLBSLocationManager 类,由此访问authorizationStatus,accuracyAuthorization
 */
- (void)tencentLBSDidChangeAuthorization:(TencentLBSLocationManager *)manager{
    
    //    - 如果定位精度权限变更为精确的时候,再次更新定位信息

    if (@available(iOS 14.0, *)) {
        if(    self.locationManager.accuracyAuthorization == CLAccuracyAuthorizationFullAccuracy){
            
            
            
            
            if(       self.isrequestTemporaryFullAccuracyAuthorizationWithPurposeKey == YES){// 首次请求
                
                self.isrequestTemporaryFullAccuracyAuthorizationWithPurposeKey = NO;
                
                // 获取定位
                [self requestLocation];
                
                
            }
            
            
            
            
            
            
            
        }
    } else {

}
    
    
    
}

    

注意:仅当TencentLBSRequestLevel为TencentLBSRequestLevelPoi有返回值,否则为空。

代码语言:javascript复制
/**
 *  返回当前位置周围的POI
 *  仅当TencentLBSRequestLevel为TencentLBSRequestLevelPoi有返回值,否则为空
 */
@property (nonatomic, strong, nullable) NSArray<TencentLBSPoi*> *poiList;


错误信息

代码语言:javascript复制
typedef NS_ENUM(NSUInteger, TencentLBSLocationError) {
    TencentLBSLocationErrorUnknown = 0,                 //!< 错误码,表示目前位置未知,但是会一直尝试获取
    TencentLBSLocationErrorDenied = 1,                  //!< 错误码,表示定位权限被禁止
    TencentLBSLocationErrorNetwork = 2,                 //!< 错误码,表示网络错误
    TencentLBSLocationErrorHeadingFailure = 3,          //!< 错误码,表示朝向无法确认
    TencentLBSLocationErrorOther = 4,                   //!< 错误码,表示未知错误
};

1.2 高德SDK

  1. 添加依赖 pod 'AMapLocation', '2.6.7'
  2. 导入头文件:``
代码语言:javascript复制
- (void)setuprequestLocationWithAMapLocatingCompletionBlock:(AMapLocatingCompletionBlock)completionBlock{
        
    self.block =completionBlock;
    self.location = [[AMapLocationManager alloc]init];
    self.location.delegate = self;
    
    [SVProgressHUD showWithStatus:@"定位中.."];

    if (@available(iOS 14.0, *)) {
        self.location.locationAccuracyMode = AMapLocationFullAndReduceAccuracy;
    } else {
        // Fallback on earlier versions
    }
        
    [self.location setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
    //   定位超时时间,最低2s,此处设置为2s
    self.location.locationTimeout = 2;
    //   逆地理请求超时时间,最低2s,此处设置为2s
    self.location.reGeocodeTimeout = 2;
    
    [self.location requestLocationWithReGeocode:YES completionBlock:^(CLLocation *location, AMapLocationReGeocode *regeocode, NSError *error) {
       
            [SVProgressHUD dismiss];
        

        if(completionBlock){
            
            completionBlock(location,regeocode,error);
            
            
        }
    }];

}

错误信息分析

代码语言:javascript复制
///AMapLocation errorCode
typedef NS_ENUM(NSInteger, AMapLocationErrorCode)
{
    AMapLocationErrorUnknown = 1,               ///<未知错误
    AMapLocationErrorLocateFailed = 2,          ///<定位错误
    AMapLocationErrorReGeocodeFailed  = 3,      ///<逆地理错误
    AMapLocationErrorTimeOut = 4,               ///<超时
    AMapLocationErrorCanceled = 5,              ///<取消
    AMapLocationErrorCannotFindHost = 6,        ///<找不到主机
    AMapLocationErrorBadURL = 7,                ///<URL异常
    AMapLocationErrorNotConnectedToInternet = 8,///<连接异常
    AMapLocationErrorCannotConnectToHost = 9,   ///<服务器连接失败
    AMapLocationErrorRegionMonitoringFailure=10,///<地理围栏错误
    AMapLocationErrorRiskOfFakeLocation = 11,   ///<存在虚拟定位风险
    AMapLocationErrorNoFullAccuracyAuth = 12,   ///<精确定位权限异常
};

II 判断经纬度是否在国内

2.1 判断经纬度是否在国内

代码语言:javascript复制
TencentLBSLocationUtils
/**
 *  判断经纬度是否在国内
 *  
 */
  (BOOL) isInRegionWithLatitude:(double)latitude longitude:(double)longitude;


https://blog.csdn.net/z929118967/article/details/120510396

封装

代码语言:javascript复制

/**
 
 根据经纬度判断 是否在大陆地区
 */
  (BOOL)inChineseMainlandWithCLLocation:(CLLocation *)location province:(NSString *)province{
    //coordinate.longitude
    
    if([TencentLBSLocationUtils isInRegionWithLatitude:location.coordinate.latitude longitude:location.coordinate.longitude]){
        
        // 排除 香港、澳门、台湾
        if([province isEqualToString:@"香港特别行政区"] || [province isEqualToString:@"澳门特别行政区"] || [province isEqualToString:@"台湾"]){
            
            return NO;

            
        }else{
            return YES;

        }
        
        
        
    }else{//其他地区
        
        return NO;

    }
    return YES;
}


2.2 根据定位返回信息判断当前位置的行政区

代码语言:javascript复制
/**
 *  返回当前位置的行政区划, 0-表示中国大陆、港、澳, 1-表示其他
 */
@property (nonatomic, assign) NSInteger areaStat;


III 封装定位SDK,统一数据模型

为了便于灵活切换,新增定位统一入口,用于封装腾讯SDK和高德SDK,便于灵活切换。

代码语言:javascript复制
        [CRMLBSManager singleLocationIsShowError:YES WithcompletionBlock:^(CRMLBSLocationModel * _Nonnull location) {
            
        
            if(location.error){
                return;
                
            }
            
            if(!location.isInRegion){
                [SVProgressHUD showInfoWithStatus:@"您最新定位不在支持范围内!"];
                return;

                
            }            
                    weakSelf.locationView.adressLab.text = location.address;

        }];
    }];
    

3.1 定位数据模型

代码语言:javascript复制
@interface CRMLBSLocationModel : NSObject

/**
 *  返回当前位置的CLLocation信息
 */
@property (nonatomic, strong) CLLocation *location;


/**
 是否在国内,排除港、澳、台
 */
@property (nonatomic, assign) BOOL isInRegion;

/**
 *  返回当前位置的地址
 */
@property (nonatomic, copy, nullable) NSString *address;



/**
 *  返回当前位置的省份
 */
@property (nonatomic, copy, nullable) NSString *province;


/**
 *  返回当前位置的街道
 */
@property (nonatomic, copy, nullable) NSString *street;

/**
 *  返回当前位置的街道编码
 */
@property (nonatomic, copy, nullable) NSString *street_no;


///兴趣点名称
@property (nonatomic, copy) NSString *POIName;

@property (nonatomic, strong) NSError  *error;

@end

3.2 封装腾讯SDK的定位数据

代码语言:javascript复制
/**
 isShowError: 发生错误是否展示提示语
 completionBlock: 定位回调
 */
  (void)singleLocationIsShowError:(BOOL)isShowError WithcompletionBlock:(void (^)(CRMLBSLocationModel * _Nonnull location))completionBlock{
    
    [ERPLBS.shareERPLBS SingleLocation:^(TencentLBSLocation * _Nullable location, NSError * _Nullable error) {
        if ([SVProgressHUD isVisible]) {
            [SVProgressHUD dismiss];
        }

        CRMLBSLocationModel *model = [CRMLBSLocationModel new];
        
        if(error){//TencentLBSLocationError
            
            NSLog(@"locError:{%ld - %@};", (long)error.code, error.localizedDescription);

            if(isShowError){
                NSString *errorInfo = error.userInfo[@"NSLocalizedDescription"];
                errorInfo=errorInfo?errorInfo:@"定位失败请重新再试!";
    
                [SVProgressHUD showErrorWithStatus:errorInfo];
                //           [self showHUDMessage:errorInfo];
                
            }
            
            model.error = error;
            if(completionBlock){
                completionBlock(model);
            }
            return;
        }
        
        // 定位成功

        if (!location)
        {
            if(completionBlock){
                completionBlock(model);
            }
            return;
        }
        NSLog(@"location:%@", location);

        model.street = location.street;
        model.street_no = location.street_no;
        model.address= location.address;
        model.location = location.location;//经纬度
        model.POIName =[location.poiList.firstObject name];//[ERPLBS POInamebyArr:location.poiList]
        model.province = location.province;//
        // 判断是否在大陆 areaStat
        model.isInRegion= [ERPLBS inChineseMainlandWithCLLocation:location.location province:location.province];
        if(completionBlock){
            completionBlock(model);
        }

    }];
    
    
}


3.3 封装高德SDK的定位数据

代码语言:javascript复制
  (void)singleLocation4AMapIsShowError:(BOOL)isShowError WithcompletionBlock:(void (^)(CRMLBSLocationModel * _Nonnull location))completionBlock{
    
    
    [[ProjectMethod shareProjectMethod] SingleLocation:^(CLLocation *location, AMapLocationReGeocode *regeocode, NSError *error) {
        
        if ([SVProgressHUD isVisible]) {
            [SVProgressHUD dismiss];
        }

        CRMLBSLocationModel *model = [CRMLBSLocationModel new];

        if(error){//AMapLocationErrorCode
            
            NSLog(@"locError:{%ld - %@};", (long)error.code, error.localizedDescription);

            if(isShowError){
                NSString *errorInfo = error.userInfo[@"NSLocalizedDescription"];
                errorInfo=errorInfo?errorInfo:@"定位失败请重新再试!";
    
                [SVProgressHUD showErrorWithStatus:errorInfo];
                //           [self showHUDMessage:errorInfo];
                
            }
            
            model.error = error;
            if(completionBlock){
                completionBlock(model);
            }
            return;
        }
        
        // 定位成功

        if (!location)
        {
            if(completionBlock){
                completionBlock(model);
            }
            return;
        }
        NSLog(@"location:%@", location);

        if (!regeocode)
        {
            if(completionBlock){
            completionBlock(model);
        }
            return;
            
        }
        NSLog(@"reGeocode:%@", regeocode);

        // 判断是否在大陆
        model.isInRegion= [ERPAMapLocationTool inChineseMainlandWithCLLocation:location regeocode:regeocode];
        
        model.location = location;//经纬度
        
        model.street = regeocode.street;
        
        model.street_no = regeocode.number;


        model.address=[NSString stringWithFormat:@"%@%@%@%@%@",regeocode.province,regeocode.city,regeocode.district,regeocode.street,regeocode.POIName];
        //regeocode.formattedAddress;//
        model.POIName =regeocode.POIName;
    
        model.province = regeocode.province;//

        if(completionBlock){
            completionBlock(model);
        }
        
    }];
    
    
    
}

3.4 统一定位入口

代码语言:javascript复制
#define k_TENCENTLBS NO// 控制定位类型

/**
 isShowError: 发生错误是否展示提示语
 completionBlock: 定位回调
 */
  (void)singleLocationIsShowError:(BOOL)isShowError WithcompletionBlock:(void (^)(CRMLBSLocationModel * _Nonnull location))completionBlock{
    NSLog(@"统一定位入口");
    
    //
    
    if(!k_TENCENTLBS){
        // 调用高德SDK
        NSLog(@"调用高德SDK");
        [self singleLocation4AMapIsShowError:isShowError WithcompletionBlock:completionBlock];
        return;
    }
    NSLog(@"调用腾讯SDK");
}
    

0 人点赞