【问题标题】:Local Notifications only received once (iBeacons)本地通知只收到一次(iBeacons)
【发布时间】:2013-11-09 08:00:24
【问题描述】:

我想在 iPad 应用程序中使用 iBeacon 功能作为信标发射器,并使用 iphone 应用程序来接收信标。我能够相应地构建这两个应用程序,但现在我遇到了一些奇怪的问题:

iBeacon 发射器 iPad 应用程序充当信标信号的发射器。我实现了一个用于选择我想传输的信标 ID 的操作表。这是它的代码:

#import "BeaconAdvertisingService.h"
@import CoreBluetooth;

NSString *const kBeaconIdentifier = @"identifier";

@interface BeaconAdvertisingService () <CBPeripheralManagerDelegate>
@property (nonatomic, readwrite, getter = isAdvertising) BOOL advertising;
@end

@implementation BeaconAdvertisingService {
    CBPeripheralManager *_peripheralManager;
}

+ (BeaconAdvertisingService *)sharedInstance {
    static BeaconAdvertisingService *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];

    return self;
}

- (BOOL)bluetoothStateValid:(NSError **)error {
    BOOL bluetoothStateValid = YES;
    switch (_peripheralManager.state) {
        case CBPeripheralManagerStatePoweredOff:
            if (error != NULL) {
                *error = [NSError errorWithDomain:@"identifier.bluetoothState"
                                             code:CBPeripheralManagerStatePoweredOff
                                         userInfo:@{@"message": @"You must turn Bluetooth on in order to use the beacon feature"}];
            }
            bluetoothStateValid = NO;
            break;
        case CBPeripheralManagerStateResetting:
            if (error != NULL) {
                *error = [NSError errorWithDomain:@"identifier.bluetoothState"
                                             code:CBPeripheralManagerStateResetting
                                         userInfo:@{@"message" : @"Bluetooth is not available at this time, please try again in a moment."}];
            }
            bluetoothStateValid = NO;
            break;
        case CBPeripheralManagerStateUnauthorized:
            if (error != NULL) {
                *error = [NSError errorWithDomain:@"identifier.bluetoothState"
                                             code:CBPeripheralManagerStateUnauthorized
                                         userInfo:@{@"message": @"This application is not authorized to use Bluetooth, verify your settings or check with your device's administration"}];
            }
            bluetoothStateValid = NO;
            break;
        case CBPeripheralManagerStateUnknown:
            if (error != NULL) {
                *error = [NSError errorWithDomain:@"identifier.bluetoothState"
                                             code:CBPeripheralManagerStateUnknown
                                         userInfo:@{@"message": @"Bluetooth is not available at this time, please try again in a moment"}];
            }
            bluetoothStateValid = NO;
            break;
        case CBPeripheralManagerStateUnsupported:
            if (error != NULL) {
                *error = [NSError errorWithDomain:@"identifier.blueetoothState"
                                             code:CBPeripheralManagerStateUnsupported
                                         userInfo:@{@"message": @"Your device does not support bluetooth. You will not be able to use the beacon feature"}];
            }
            bluetoothStateValid = NO;
            break;
        case CBPeripheralManagerStatePoweredOn:
            bluetoothStateValid = YES;
            break;
    }
    return bluetoothStateValid;
}

- (void)startAdvertisingUUID:(NSUUID *)uuid major:(CLBeaconMajorValue)major minor:(CLBeaconMinorValue)minor {
    NSError *bluetoothStateError = nil;
    if (![self bluetoothStateValid:&bluetoothStateError]) {
        NSString *title = @"Bluetooth Issue";
        NSString *message = bluetoothStateError.userInfo[@"message"];

        [[[UIAlertView alloc] initWithTitle:title
                                    message:message
                                   delegate:nil
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil] show];
        return;
    }

    CLBeaconRegion *region;
    if (uuid && major && minor) {
        region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid major:major minor:minor identifier:kBeaconIdentifier];
    } else if (uuid && major) {
        region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid major:major identifier:kBeaconIdentifier];
    } else if (uuid) {
        region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:kBeaconIdentifier];
    } else {
        [NSException raise:@"You must at least provide a UUID to start advertising" format:nil];
    }

    NSDictionary *peripheralData = [region peripheralDataWithMeasuredPower:nil];
    [_peripheralManager startAdvertising:peripheralData];
}

- (void)stopAdvertising {
    [_peripheralManager stopAdvertising];
    self.advertising = NO;
}

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    NSError *bluetoothStateError = nil;
    if (![self bluetoothStateValid: &bluetoothStateError]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *bluetoothIssueAlert = [[UIAlertView alloc] initWithTitle:@"Bluetooth Problem"
                                                                          message:bluetoothStateError.userInfo[@"message"]
                                                                         delegate:nil
                                                                cancelButtonTitle:@"OK"
                                                                 otherButtonTitles:nil];
            [bluetoothIssueAlert show];
        });
    }
}

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {
    dispatch_async(dispatch_get_main_queue(), ^{
        if (error) {
            [[[UIAlertView alloc] initWithTitle:@"Cannot Advertise Beacon"
                                        message:@"There was an issue starting the advertisement of the beacon"
                                       delegate:nil
                              cancelButtonTitle:@"OK"
                              otherButtonTitles:nil] show];
        } else {
            NSLog(@"Advertising");
            self.advertising = YES;
        }
    });
}

据我所知,传输工作非常好......

我想响应收到的信号 ID 的 iphone 应用程序应在收到 ID 后立即引发本地通知。这在第一次运行时效果很好。我可以选择 ipad 操作表中的 3 个信标中的每一个来在 iphone 上发送此通知。但是,例如,当我重新选择第一个信标时,什么也没有发生。对于应用程序而言,应用程序每次收到信标时都做出响应至关重要。我将iphone代码设置如下:

#import "BeaconMonitoringService.h"
#import "LocationManagerService.h"

@implementation BeaconMonitoringService {
    CLLocationManager *_locationManager;
}

+ (BeaconMonitoringService *)sharedInstance {
    static dispatch_once_t onceToken;
    static BeaconMonitoringService *_sharedInstance;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    _locationManager = [[LocationManagerService sharedInstance] getLocationManager];
    return self;
}

- (void)startMonitoringBeaconWithUUID:(NSUUID *)uuid major:(CLBeaconMajorValue)major minor:(CLBeaconMinorValue)minor identifier:(NSString *)identifier onEntry:(BOOL)entry onExit:(BOOL)exit {
    CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid major:major minor:minor identifier:identifier];
    region.notifyOnEntry = entry;
    region.notifyOnExit = exit;
    region.notifyEntryStateOnDisplay = YES;
    [_locationManager startMonitoringForRegion:region];
}

- (void)stopMonitoringAllRegions {
    for (CLRegion *region in _locationManager.monitoredRegions) {
        [_locationManager stopMonitoringForRegion:region];
    }
}

@end

位置管理器相应地抛出其委托调用,并由我在 locationmanagerservice 中实现。

- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
    if ([region isKindOfClass:[CLBeaconRegion class]]) {
        CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
        Beacon *beacon = [[BeaconDetailService sharedService] beaconWithUUID:beaconRegion.proximityUUID];
        if (beacon) {
            NSDictionary *userInfo = @{@"beacon": beacon, @"state": @(state)};
            [[NSNotificationCenter defaultCenter] postNotificationName:@"DidDetermineRegionState" object:self userInfo:userInfo];
        }

        NSLog(@"Call DidDetermine");
    }
}

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([region isKindOfClass:[CLBeaconRegion class]]) {
            CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
            Beacon *beacon = [[BeaconDetailService sharedService] beaconWithUUID:beaconRegion.proximityUUID];
            if (beacon) {
                UILocalNotification *notification = [[UILocalNotification alloc] init];
                notification.userInfo = @{@"uuid": beacon.uuid.UUIDString};
                notification.alertBody = [NSString stringWithFormat:@"Test Beacon %@", beacon.name];
                notification.soundName = @"Default";
                [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
                [[NSNotificationCenter defaultCenter] postNotificationName:@"DidEnterRegion" object:self userInfo:@{@"beacon": beacon}];

                NSLog(@"Call DidEnter");
            }
        }
    });
}

- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([region isKindOfClass:[CLBeaconRegion class]]) {
            CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
            Beacon *beacon = [[BeaconDetailService sharedService] beaconWithUUID:beaconRegion.proximityUUID];
            if (beacon) {
                UILocalNotification *notification = [[UILocalNotification alloc] init];
                notification.alertBody = [NSString stringWithFormat:@"Test %@", beacon.name];
                [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
                [[NSNotificationCenter defaultCenter] postNotificationName:@"DidExitRegion" object:self userInfo:@{@"beacon": beacon}];
                NSLog(@"Call DidExit");
            }
        }
    });
}

当我记录委托方法的调用时,我收到以下方案:

1) DidDetermineState 被调用 2) 正在调用 DidEnterRegion 3) 但之后没有调用 DidExitRegion。

我也反复收到此错误: "PBRequester failed with Error Error Domain=NSURLErrorDomain Code=-1003 "找不到指定主机名的服务器。" UserInfo=0x166875e0 {NSErrorFailingURLStringKey=https://gsp10-ssl.apple.com/use, NSErrorFailingURLKey=https://gsp10-ssl.apple.com/use, NSLocalizedDescription=指定主机名的服务器找不到。, NSUnderlyingError=0x1656a9b0 "找不到指定主机名的服务器。"}"

这看起来很奇怪.. 每次我在 ipad 上的操作表中选择信标时,有没有一种方法可以接收本地注释?

有趣的是,当我打开我选择的 Beacon 时,我的 iphone 会时不时地抛出本地注释,而我在两者之间没有更改任何内容。突然间,DidExitRegion 被调用,DidEnterRegion 再次被调用......

谢谢!!

【问题讨论】:

    标签: uilocalnotification ibeacon


    【解决方案1】:

    如果不了解您在做什么,就很难准确地说出发生了什么。您是在持续传输 3 个 UUID 的广告,还是只传输一次然后停止? “即使我删除了已发送的旧文件并且应该从头开始”是什么意思?这是否意味着停止对这些 UUID 进行测距然后重新启动?一些代码 sn-ps 可能会有所帮助。

    要知道的重要一点是,即使没有监控处于活动状态,iOS 也会继续跟踪它看到的每个 I 信标的状态。这意味着,如果 iOS 检测到 iBeacons A 和 B 在您开始监视它们之前,您将不会收到 didEnterRegion 通知。同样,如果在收到此类通知后您停止监控并重新启动监控,您也不会收到新的 didEnterRegion 通知,直到 iOS 认为 iBeacon 消失并重新出现。

    您确定这种完整的过渡正在发生吗?尝试在 didEnterRegion 和 didExitRegion 回调中添加 NSLog 语句,以帮助查看事件触发的时间戳。

    同样重要的是要了解,如果前台没有具有活动 iBeacon 监视器或范围的应用程序,iOS 可能需要很长时间才能检测到状态变化。我已经看到每个状态转换最多需要 4 分钟。如果您正在启动和停止发射器,请尝试至少等待 8 分钟再重新启动,并通过日志验证您是否获得了第二次通知所需的两个转换。

    【讨论】:

    • 嗨,大卫,我一直在不断地传输它们。当我点击 ipad 应用程序上的“停止”按钮时,我调用了 stopMonitoringAllRegions 方法,正如您在代码中看到的那样。如何强制 iOS 放弃对信标的跟踪,以便下次再次选择信标时可以“重新开始”?有趣的是,DidEnterRegion 和 DidDetermineState 仅在我第一次选择 ipad 上的信标时被调用。我第二次选择它时,屏幕上没有再次记录任何内容..因此不再调用委托。
    • 感谢您的编辑,这些帮助。不幸的是,我认为没有任何方法可以“强制”iOS 重新开始。 iBeacons 的跟踪是在操作系统级别处理的,并且不允许应用程序操纵此跟踪。调用 stopMonitoringAllRegions 方法不会影响操作系统级别跟踪的状态。您只需等待足够的时间(如果在后台,则至少等待 4 分钟),然后再重新开始传输。
    • 但是如果我们以一个购物中心为例,你可能有数百个信标,它们会不断传输信号,你会有一个用户在四处闲逛,他会根据需要接收当地的笔记,然后转身回去……那他就再也收不到了?
    • 我遇到的另一个奇怪的行为是,即使我没有停止传输,信标状态突然从洞察变为未知。这是正常行为吗?
    • 是的,我还看到 iOS 有时会调用 didExitRegion,然后几乎立即调用 didEnterRegion。我不确定是什么原因造成的,但是您可以通过将上次退出该区域的时间存储在 NSDate 变量中来在代码中过滤掉这些。当您收到 didEnterRegion 回调时,如果退出时间戳在最后几秒内,您只需忽略它。至于您对购物中心用例的其他评论,请在此处查看我的回答:stackoverflow.com/questions/19477044/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多