2013-10-29 61 views
1

我想在iPad應用程序中使用iBeacon功能來充當信標發射器和iphone應用程序來接收信標。我是能夠建立相應的這兩個應用,但現在我遇到了一些奇怪的問題:本地通知只收到一次(iBeacons)

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失敗,錯誤錯誤域= NSURLErrorDomain代碼= -1003‘與指定的主機服務器無法找到’的UserInfo = 0x166875e0 {NSErrorFailingURLStringKey = https://gsp10-ssl.apple.com/use,NSErrorFailingURLKey = https://gsp10-ssl.apple.com/use, NSLocalizedDescription =找不到指定主機名的服務器,NSUnderlyingError = 0x1656a9b0「找不到指定主機名的服務器」}「

這似乎很奇怪..有沒有一種方法可以完成每次我在我的iPad上的操作表中選擇一個燈塔時,都會收到本地筆記?

有趣的是,當我離開燈塔時,我選擇打開,我的iphone不斷拋出本地音符,而不用改變它們之間的任何東西。突然之間DidExitRegion被稱爲和DidEnterRegion之後,再次...

謝謝你!

回答

2

很難確切地說出發生了什麼事,而不知道你正在做什麼。你是否不斷地傳送3個UUID的廣告,或者只是傳送一次然後停止? 「即使我刪除已發送的舊文件,並應該從一個乾淨的文件開始」,你是什麼意思?這是否意味着停止測試這些UUID然後重新啓動?一些代碼片段可能會有所幫助。

需要了解的重要一點是,即使沒有監視器處於活動狀態,iOS也會繼續跟蹤它所看到的每個信標的狀態。這意味着,如果在之前您已經通過iOS 檢測到iBeacons A和B,您將開始監控它們,您將不會收到didEnterRegion通知。同樣,如果在收到此類通知後停止監控並重新啓動監控,則在iOS認爲iBeacon消失並重新出現之前,您也不會收到新的didEnterRegion通知。

你確定這個完整的過渡正在發生?嘗試在didEnterRegion和didExitRegion回調中添加NSLog語句以幫助查看事件觸發時間的時間戳。

瞭解如果沒有帶有活動iBeacon監視器或範圍的應用程序處於前臺,iOS可能需要很長時間來檢測狀態更改也很重要。我已經看到每個州轉換需要4分鐘的時間。如果您正在啓動和停止變送器,請嘗試至少等待8分鐘後再重新啓動,然後通過日誌進行驗證,然後獲得第二次通知所需的兩個轉換。

+0

嗨大衛,以及我正在不斷傳輸他們,據我所知。當我點擊我的iPad應用程序上的「停止」按鈕時,我會調用stopMonitoringAllRegions方法,正如您在代碼中看到的那樣。 如何強制iOS鬆開信標的跟蹤,以便下次再次選擇信標時我有一個「新鮮開始」?有趣的是,DidEnterRegion和DidDetermineState只在我第一次選擇ipad上的beacon時被調用。第二次我選擇它時,屏幕上沒有再次記錄任何內容..所以代理不會再被調用。 – sesc360

+1

感謝您的編輯,這些幫助。不幸的是,我認爲沒有辦法「強制」iOS開始新鮮事。 iBeacons的跟蹤在操作系統級別進行處理,應用程序不允許操作此跟蹤。調用stopMonitoringAllRegions方法不會影響操作系統級別跟蹤的狀態。您只需等待足夠的時間(至少4分鐘,如果在後臺),然後重新開始傳輸。 – davidgyoung

+0

但是,如果我們以購物中心爲例,您可能會有數百個信標不斷傳輸信號,並且您將有一位用戶漫步,他會收到當地的筆記,然後轉身走開回來......那麼他將無法再接收他們呢? – sesc360