我需要什么:
当应用程序耗尽且设备已插入时,可启动iBeacon委托方法(如didDetermineState
,didRangeBeacons
,didEnterRegion
或didExitRegion
的可预测,可靠且可靠的方式在附近。
现状
我正在制作一个应用程序供家长用于帮助他们在重要时间关闭手机。该应用程序采用Objective-C,即使在应用程序生命周期之后,它仍需要与蓝牙设备保持持久连接。
我已经尝试了很长时间才能让这个工作起来,而且我得到了很多S.O.的帮助。海报,目前我知道我必须在我的设备中使用iBeacon从终止启动(这是我使用它的唯一原因,如果有另一种方式从终止启动应用程序,我很乐意转储它)。为了澄清,我需要在同一个设备(我已经建立)iBeacon和一个可靠的BT连接的2件事。我需要此设备连接配对,因为这是从BT设备发送/接收命令的唯一方法。我发现,在后台触发的didRange
或didEnter
委托方法充其量是不可靠的。他们不会马上开火,他们只会开火几次而且整个事情都会消失(我现在知道这个10秒窗口是终止应用的预期行为)。我甚至整天都在插拔电源,不断寻找应用已恢复生命的任何迹象,没有任何反应......
当应用程序打开时,工作正常,但是当应用程序在我的信标/蓝牙附近时,我希望它在应用程序内部启动一种临时锁定屏幕。当应用程序处于前台时,我已经相当好地完成了这一部分。如果一个孩子试图关闭应用程序或后台,我想通过让我的BT设备在终止后启动到后台进行响应(我知道用户界面不会出现并且那很好我只需要一系列功能来解雇)。然后它将连接到蓝牙并从设备接收一些命令。听起来很简单呃?事情变得混乱了。
一些上下文:我在info.plist中为蓝牙和信标添加了所有后台模式,当应用处于前台时,一切正常......
如果在范围内检测到iBeacon,我想使用该10秒窗口通过BT配对连接到我的盒子并通过命令发送。到目前为止,这是不可思议的...当应用程序终止时,iBeacon测距函数不会触发它们只会触发最奇怪的用例。我似乎无法预测它们什么时候开火。
ibeaconManager.h
@interface IbeaconManager : NSObject
@property (nonatomic) BOOL waitingForDeviceCommand;
@property (nonatomic, strong) NSTimer *deviceCommandTimer;
+ (IbeaconManager *) sharedInstance;
- (void)startMonitoring;
- (void)stopMonitoring;
- (void)timedLock:(NSTimer *)timer;
@end
ibeaconManager.m
@interface IbeaconManager () <CLLocationManagerDelegate>
@property (nonatomic, strong) BluetoothMgr *btManager;
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) CLBeaconRegion *region;
@property (nonatomic) BOOL connectedToDevice;
@end
NSString *const PROXMITY_UUID = @"00000000-1111-2222-3333-AAAAAAAAAAAA";
NSString *const BEACON_REGION = @"MY_CUSTOM_REGION";
const int REGION_MINOR = 0;
const int REGION_MAJOR = 0;
@implementation IbeaconManager
+ (IbeaconManager *) sharedInstance {
static IbeaconManager *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[IbeaconManager alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if(self) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager requestAlwaysAuthorization];
self.connectedToDevice = NO;
self.waitingForDeviceCommand = NO;
self.region = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:PROXMITY_UUID]
major:REGION_MAJOR
minor:REGION_MINOR
identifier:BEACON_REGION];
self.region.notifyEntryStateOnDisplay = YES;
self.region.notifyOnEntry = YES;
self.region.notifyOnExit = YES;
}
return self;
}
- (void)startMonitoring {
if(self.region != nil) {
NSLog(@"**** started monitoring with beacon region **** : %@", self.region);
[self.locationManager startMonitoringForRegion:self.region];
[self.locationManager startRangingBeaconsInRegion:self.region];
}
}
- (void)stopMonitoring {
NSLog(@"*** stopMonitoring");
if(self.region != nil) {
[self.locationManager stopMonitoringForRegion:self.region];
[self.locationManager stopRangingBeaconsInRegion:self.region];
}
}
- (void)triggerCustomLocalNotification:(NSString *)alertBody {
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = alertBody;
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
}
#pragma mark - CLLocationManager delegate methods
- (void)locationManager:(CLLocationManager *)manager
didDetermineState:(CLRegionState)state
forRegion:(CLRegion *)region {
NSLog(@"did determine state STATE: %ld", (long)state);
NSLog(@"did determine state region: %@", region);
[self triggerCustomLocalNotification:@"made it into the did determine state method"];
NSUInteger appState = [[UIApplication sharedApplication] applicationState];
NSLog(@"application's current state: %ld", (long)appState);
if(appState == UIApplicationStateBackground || appState == UIApplicationStateInactive) {
NSString *notificationText = @"Did range beacons... The app is";
NSString *notificationStateText = (appState == UIApplicationStateInactive) ? @"inactive" : @"backgrounded";
NSString *notificationString = [NSString stringWithFormat:@"%@ %@", notificationText, notificationStateText];
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
bool isAppLockScreenShowing = [userDefaults boolForKey:@"isAppLockScreenShowing"];
if(!isAppLockScreenShowing && !self.waitingForDeviceCommand) {
self.waitingForDeviceCommand = YES;
self.deviceCommandTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(timedLock:)
userInfo:notificationString
repeats:NO];
}
} else if(appState == UIApplicationStateActive) {
if(region != nil) {
if(state == CLRegionStateInside) {
NSLog(@"locationManager didDetermineState INSIDE for %@", region.identifier);
[self triggerCustomLocalNotification:@"locationManager didDetermineState INSIDE"];
} else if(state == CLRegionStateOutside) {
NSLog(@"locationManager didDetermineState OUTSIDE for %@", region.identifier);
[self triggerCustomLocalNotification:@"locationManager didDetermineState OUTSIDE"];
} else {
NSLog(@"locationManager didDetermineState OTHER for %@", region.identifier);
}
}
//Upon re-entry, remove timer
if(self.deviceCommandTimer != nil) {
[self.deviceCommandTimer invalidate];
self.deviceCommandTimer = nil;
}
}
}
- (void)locationManager:(CLLocationManager *)manager
didRangeBeacons:(NSArray *)beacons
inRegion:(CLBeaconRegion *)region {
NSLog(@"Did range some beacons");
NSUInteger state = [[UIApplication sharedApplication] applicationState];
NSString *notificationStateText = (state == UIApplicationStateInactive) ? @"inactive" : @"backgrounded";
NSLog(@"application's current state: %ld", (long)state);
[self triggerCustomLocalNotification:[NSString stringWithFormat:@"ranged beacons, application's current state: %@", notificationStateText]];
if(state == UIApplicationStateBackground || state == UIApplicationStateInactive) {
NSString *notificationText = @"Did range beacons... The app is";
NSString *notificationString = [NSString stringWithFormat:@"%@ %@", notificationText, notificationStateText];
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
bool isAppLockScreenShowing = [userDefaults boolForKey:@"isAppLockScreenShowing"];
if(!isAppLockScreenShowing && !self.waitingForDeviceCommand) {
self.waitingForDeviceCommand = YES;
self.deviceCommandTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(timedLock:)
userInfo:notificationString
repeats:NO];
}
} else if(state == UIApplicationStateActive) {
if(self.deviceCommandTimer != nil) {
[self.deviceCommandTimer invalidate];
self.deviceCommandTimer = nil;
}
}
}
- (void)timedLock:(NSTimer *)timer {
self.btManager = [BluetoothMgr sharedInstance];
[self.btManager sendCodeToBTDevice:@"magiccommand"
characteristic:self.btManager.lockCharacteristic];
[self triggerCustomLocalNotification:[timer userInfo]];
self.waitingForDeviceCommand = NO;
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(@"Did Enter Region: %@", region);
[self triggerCustomLocalNotification:[NSString stringWithFormat:@"Did enter region: %@", region.identifier]];
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(@"Did Exit Region: %@", region);
[self triggerCustomLocalNotification:[NSString stringWithFormat:@"Did exit region: %@", region.identifier]];
//Upon exit, remove timer
if(self.deviceCommandTimer != nil) {
[self.deviceCommandTimer invalidate];
self.deviceCommandTimer = nil;
}
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSLog(@"monitoringDidFailForRegion EPIC FAIL for region %@ withError %@", region.identifier, error.localizedDescription);
}
@end
答案 0 :(得分:2)
我为iOS构建了一个类似的系统,它使用iBeacon传输在后台唤醒,然后连接到蓝牙LE以交换数据。 请放心,这一切都是可能的,让工作变得棘手并且调试起来比较棘手。
使用蓝牙LE连接的一些提示:
当应用程序被杀死时,灯标范围函数不会触发除非您还监视信标并获得didEnter
或didExit
转换, - 如您所述,将应用程序启动到后台10秒。同样,只有当您从区域转移到区域外或反之亦然时,才会发生这种情况。这很难测试,因为你可能没有意识到CoreLocation认为你是&#34;在地区&#34;当你杀死应用程序时,你却没有获得用于检测信标的唤醒事件。
为了在后台获取蓝牙事件,您需要确保您的Info.plist声明:
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
</array>
如果不存在,您绝对不会在后台获得didDiscoverPeripheral
的回调。
您需要在应用启动时开始扫描蓝牙,并在收到回复func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
时连接
从上面保存peripheral
实例的副本,因为您只能在后台获得一个回调,以便从每个独特的蓝牙设备进行发现。如果连接失败,则可以使用相同的peripheral
对象实例重试。
为了调试从已杀死状态重新启动,我添加了许多NSLog
语句(我添加了在代码中打开和关闭它们的功能),然后在XCode&#中查找这些语句39; s Windows - &gt;设备 - &gt;我的iPhone面板,您可以在其中展开屏幕底部的小箭头,以显示设备上所有应用的日志。如果你的应用程序从被杀死状态重新启动,你绝对会看到这里的日志。