类似钉钉打卡功能

我们的打卡功能分为地理位置打卡和Wi-Fi打卡
1、地理位置打卡:用户必须到达指定的地理围栏内打卡,才能正常打卡成功,否则为外勤打卡。
2、Wi-Fi打卡:用户必须连接到指定Wi-Fi,才能打卡成功
当然还会有外勤打卡的情况:没有在指定地理围栏内,并且没有连接指定Wi-Fi。

一、地理位置打卡,就不得不提到地理围栏的概念

我们使用的是腾讯定位SDK 腾讯定位SDK开发文档

第1步,引入头文件

在调用地理围栏功能的类中引入AMapFoundationKit.h和AMapLocationKit.h这两个头文件

#import <AMapFoundationKit/AMapFoundationKit.h>

#import <AMapLocationKit/AMapLocationKit.h>
第 2 步,配置Key

在调用定位时,需要添加Key,需要注意的是请在 SDK 任何类的初始化以及方法调用之前设置正确的 Key。

如果您使用的是定位SDK v2.x版本需要引入基础 SDK AMapLocationKit.framework ,设置apiKey的方式如下:

iOS 定位SDK v2.x版本设置 Key:

[AMapServices sharedServices].apiKey =@"您的key";
第 3 步:创建地理围栏

地理围栏没有最大个数限制,您可以无限制的创建围栏。但请您根据业务需求合理的创建围栏,控制围栏个数可以有效的保证程序执行效率。定位 SDK 提供根据高德POI、行政区划,自定义圆形、自定义多边形四种方式创建地理围栏。
1、初始化地理围栏管理manager

self.geoFenceManager = [[AMapGeoFenceManager alloc] init];
self.geoFenceManager.delegate = self; 
self.geoFenceManager.activeAction = AMapGeoFenceActiveActionInside | AMapGeoFenceActiveActionOutside | AMapGeoFenceActiveActionStayed; //设置希望侦测的围栏触发行为,默认是侦测用户进入围栏的行为,即AMapGeoFenceActiveActionInside,这边设置为进入,离开,停留(在围栏内10分钟以上),都触发回调
self.geoFenceManager.allowsBackgroundLocationUpdates = YES;  //允许后台定位

2、创建高德POI地理围栏
提供两个创建高德POI围栏的接口,一个是根据关键字创建POI围栏,另一个是根据经纬度进行周边搜索创建POI围栏。
根据关键字创建围栏:

- (void)addKeywordPOIRegionForMonitoringWithKeyword:(NSString *)keyword POIType:(NSString *)type city:(NSString *)city size:(NSInteger)size customID:(NSString *)customID;

示例代码

[self.geoFenceManager addKeywordPOIRegionForMonitoringWithKeyword:@"北京大学" POIType:@"高等院校" city:@"北京" size:20 customID:@"poi_1"];

根据周边POI创建围栏:

- (void)addAroundPOIRegionForMonitoringWithLocationPoint:(CLLocationCoordinate2D)locationPoint aroundRadius:(NSInteger)aroundRadius keyword:(NSString *)keyword POIType:(NSString *)type size:(NSInteger)size customID:(NSString *)customID;

示例代码

CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(39.908692, 116.397477); //天安门
[self.geoFenceManager addAroundPOIRegionForMonitoringWithLocationPoint:coordinate aroundRadius:10000 keyword:@"肯德基" POIType:@"050301" size:20 customID:@"poi_2"];

3、创建行政区域围栏
根据行政区域关键字创建行政区域围栏:

- (void)addDistrictRegionForMonitoringWithDistrictName:(NSString *)districtName customID:(NSString *)customID;

示例代码

[self.geoFenceManager addDistrictRegionForMonitoringWithDistrictName:@"海淀区" customID:@"district_1"];

4、创建自定义圆形围栏
需要提供中心点和半径来创建圆形围栏,一次创建一个

- (void)addCircleRegionForMonitoringWithCenter:(CLLocationCoordinate2D)center radius:(CLLocationDistance)radius customID:(NSString *)customID;

示例代码

CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(39.908692, 116.397477); //天安门
[self.geoFenceManager addCircleRegionForMonitoringWithCenter:coordinate radius:300 customID:@"circle_1"];

5、创建自定义多边形围栏
根据经纬度坐标数据添加一个闭合的多边形围栏,点与点之间按顺序尾部相连, 第一个点与最后一个点相连,一次创建一个

- (void)addPolygonRegionForMonitoringWithCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSInteger)count customID:(NSString *)customID;

示例代码

NSInteger count = 4;
CLLocationCoordinate2D *coorArr = malloc(sizeof(CLLocationCoordinate2D) * count);
coorArr[0] = CLLocationCoordinate2DMake(39.933921, 116.372927);     //平安里地铁站
coorArr[1] = CLLocationCoordinate2DMake(39.907261, 116.376532);     //西单地铁站
coorArr[2] = CLLocationCoordinate2DMake(39.900611, 116.418161);     //崇文门地铁站
coorArr[3] = CLLocationCoordinate2DMake(39.941949, 116.435497);     //东直门地铁站
[self.geoFenceManager addPolygonRegionForMonitoringWithCoordinates:coorArr count:count customID:@"polygon_1"];
    
free(coorArr);
coorArr = NULL;
第 4 步,开始定位

当围栏创建完毕,且围栏创建成功时会启动定位,这部分无需您来设置,SDK内部执行。 定位机制:通过“远离围栏时逐渐降低定位频率”来降低电量消耗,“离近围栏时逐渐提高定位频率”来保证有足够的定位精度从而完成围栏位置检测。
需要注意,在iOS9及之后版本的系统中,如果您希望程序在后台持续检测围栏触发行为,需要保证manager的allowsBackgroundLocationUpdates为YES,设置为YES的时候必须保证 Background Modes 中的 Location updates 处于选中状态,否则会抛出异常。
第 5 步,获取围栏创建后的回调和围栏状态改变时的回调

创建围栏后的信息和围栏状态改变时的信息均会通过AMapGeoFenceManagerDelegate进行回调,设置内容如下:

self.geoFenceManager.delegate = self;

1、获取围栏创建后的回调----在这里可以添加定位范围是图(高德demo)
在如下回调中知道创建的围栏是否成功,以及查看所创建围栏的具体内容。

- (void)amapGeoFenceManager:(AMapGeoFenceManager *)manager didAddRegionForMonitoringFinished:(NSArray<AMapGeoFenceRegion *> *)regions customID:(NSString *)customID error:(NSError *)error {
    if (error) {
        NSLog(@"创建失败 %@",error);
    } else {
        NSLog(@"创建成功");
    }
}

2、围栏状态改变时的回调在这里,可以获取围栏状态(在围栏内、在围栏外、在围栏内停留超过10分钟等状态)

在如下回调中知道围栏的状态是否发生改变,或者定位是否失败。围栏的状态表示的就是用户和围栏的关系,有未知、进入围栏、退出围栏、在围栏内停留。回调触发的条件需同时满足:1.围栏的状态从A变成B;2.B符合您在第一步设置的需要侦测的行为的范围内。当然如果self.geoFenceManager.activeAction在监听的过程中改变了,所有符合侦测范围的围栏即使状态没有改变也会再次触发回调。

- (void)amapGeoFenceManager:(AMapGeoFenceManager *)manager didGeoFencesStatusChangedForRegion:(AMapGeoFenceRegion *)region customID:(NSString *)customID error:(NSError *)error {
    if (error) {
        NSLog(@"status changed error %@",error);
    }else{
        NSLog(@"status changed success %@",[region description]);
    }
}

3、获取用户是否开启允许定位的权限
iOS 13以前使用这个方法获取

/**
*  @brief 定位权限状态改变时回调函数。注意:iOS13及之前版本回调
*  @param manager 定位 AMapLocationManager 类。
*  @param status 定位权限状态。
*/
- (void)amapLocationManager:(AMapLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status;

iOS 14以后使用这个方法获取

/**
 *  @brief 定位权限状态改变时回调函数。注意:iOS14及之后版本回调
 *  @param manager 定位 AMapLocationManager 类。
 *  @param locationManager  定位CLLocationManager类,可通过locationManager.authorizationStatus获取定位权限,通过locationManager.accuracyAuthorization获取定位精度权限
 */
- (void)amapLocationManager:(AMapLocationManager *)manager locationManagerDidChangeAuthorization:(CLLocationManager*)locationManager;

用户是否允许开启定位的状态------CLAuthorizationStatus

typedef NS_ENUM(int, CLAuthorizationStatus) {
      kCLAuthorizationStatusNotDetermined = 0,  // 用户未授权,即还未弹出OS的授权弹窗
      kCLAuthorizationStatusDenied, // 用户拒绝定位权限,包括拒绝App或者全局开关关闭
      kCLAuthorizationStatusRestricted, // 定位服务受限,该状态位用户无法通过设置页面进行改变
      kCLAuthorizationStatusAuthorizedAlways, // 始终定位,即后台定位
      kCLAuthorizationStatusAuthorizedWhenInUse, // App使用的时候,允许定位
      kCLAuthorizationStatusAuthorized, // iOS8.0之后已经被废弃
};

最后,移除围栏

当不再需要使用围栏时,可以调用以下几个函数对已经设定的围栏进行移除操作。

- (void)removeTheGeoFenceRegion:(AMapGeoFenceRegion *)region; //移除指定围栏
- (void)removeGeoFenceRegionsWithCustomID:(NSString *)customID; //移除指定customID的围栏
- (void)removeAllGeoFenceRegions;  //移除所有围栏

遇到的问题

1、使用定位功能,如果想使用后台定位功能,但是没有开启权限的情况下,会出现如下错误如下


截屏2021-11-10 上午10.24.52.png

解决方案:

方案一:

一种方法是处在拥有前台定位权限的情况下:

设置CLLocationManager的allowsBackgroundLocationUpdates为YES

然后打开Xcode -> Targets -> Capabilities 中的Background Modes并勾选其中的Location updates选项

截屏2021-11-10 上午10.31.17.png

原文链接:https://blog.csdn.net/feosun/article/details/77097086

方案二:

在.infoPlist文件中添加如下操作即可


截屏2021-11-10 上午10.24.41.png

二、wifi打卡

当前连接Wi-Fi的mac地址和Wi-Fi名称要和考勤Wi-Fi一致,以下是获取Wi-Fi Mac地址和Wi-Fi名称的方法
获取Wi-Fi名称

+ (NSString *)wifiName
{
    NSArray *ifs = CFBridgingRelease(CNCopySupportedInterfaces());
    id info = nil;
    for (NSString *ifname in ifs) {
        info = (__bridge_transfer id)CNCopyCurrentNetworkInfo((CFStringRef) ifname);
        if (info && [info count]) {
            break;
        }
    }
    NSDictionary *dic = (NSDictionary *)info;
    NSString *ssid = [[dic objectForKey:@"SSID"] lowercaseString];
    
    return ssid;
}

获取Wi-Fi Mac地址

+ (NSString *)wifiMac
{
    NSArray *ifs = CFBridgingRelease(CNCopySupportedInterfaces());
    id info = nil;
    for (NSString *ifname in ifs) {
        info = (__bridge_transfer id)CNCopyCurrentNetworkInfo((CFStringRef) ifname);
        if (info && [info count]) {
            break;
        }
    }
    NSDictionary *dic = (NSDictionary *)info;
    NSString *bssid = [dic objectForKey:@"BSSID"];
    return bssid;
}

只写上面两个方式是不能获取到Wi-Fi的mac地址和名称的。还需要开启Wi-Fi权限
Xcode -> [Project Name] -> Targets -> [Target Name] -> Capabilities -> Access WiFi Information -> ON


截屏2021-11-16 下午4.56.22.png

遇到的问题

1、iOS 获取到的Wi-Fi Mac地址和安卓获取到的不一样,
假设mac地址为 12:03:45:67:89:12
iOS. 获取到的结果回事12:3:45:67:89:12,会少一个0,因为iOS 默认情况下会省略首位为0 的情况

解决方案:自己拼接

+ (NSString *)wifiMac
{    
    NSString *ssid = nil;
    NSArray *ifs = (__bridge_transfer id)CNCopySupportedInterfaces();
    for (NSString *ifnam in ifs) {
        NSDictionary *info = (__bridge_transfer id)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
        if (info[@"BSSID"]) {
            ssid = info[@"BSSID"];
        }
    }
    NSArray *ssidArray = [ssid componentsSeparatedByString:@":"];
    NSMutableArray *newArray = [NSMutableArray arrayWithArray:ssidArray];
    for (NSInteger i = 0; i < ssidArray.count; i++) {
        NSString *value = ssidArray[i];
        if (value.length == 1) {
            value = [NSString stringWithFormat:@"0%@", value];
        }
        newArray[i] = value;
    }
    NSMutableString *newSSID = [NSMutableString string];
    for (NSInteger i = 0; i < newArray.count; i ++) {
        [newSSID appendString:newArray[i]];
        if (i != newArray.count - 1) {
            [newSSID appendString:@":"];
        }
        
    }
    ssid = [newSSID uppercaseString];
    return ssid;
     
}

这样,问题就解决了

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

推荐阅读更多精彩内容