how to find Bluetooth audio devices in iOS - ios

Okay, I'm working on a fun project that has a hurdle where I need to enable Bluetooth audio support for my iOS app.
The hurdle I'm at is that I simply can't even begin to get a list of connected Bluetooth audio devices. Even though my iPhone 5S recognizes my earpiece (a ~3 - 4 year old LG HBM-230, to be precise) and plays audio through it for phone calls, BOTH External Accessory and CoreBluetooth are giving me nothing useful when I query both.
I'm basing my own code off questions & answers I found for both the CoreBluetooth and External Accessory frameworks.
When my code simply tries to "scanForPeripheralsWithServices:nil" for any Bluetooth devices which Settings->Bluetooth say are visible and connected, the below code simply is NOT coming up with a single hit beyond the "CBCentralManagerStatePoweredOn" message in the console.
And this line in my code (with a valid EAAccessoryManager instance)
NSArray * connectedDevices = [self.eAAccessoryManager connectedAccessories];
also comes back with a nil array.
What could I be doing wrong?
B.T.W., I've made this code available as a GitHub project.
#implementation BluetoothManager
+ (BluetoothManager *)sharedInstance
{
static dispatch_once_t pred = 0;
__strong static id _bluetoothMGR = nil;
dispatch_once(&pred, ^{
_bluetoothMGR = [[BluetoothManager alloc] init];
});
return _bluetoothMGR;
}
- (id)init
{
self = [super init];
if(self)
{
dispatch_queue_t centralQueue = dispatch_queue_create("com.yo.mycentral", DISPATCH_QUEUE_SERIAL);
// whether we try this on a queue of "nil" (the main queue) or this separate thread, still not getting results
self.cbManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:nil];
}
return self;
}
// this would hit.... if I instantiated this in a storyboard of XIB file
- (void)awakeFromNib
{
if(!self.cbManager)
self.cbManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
NSLog(#"hey I found %#",[advertisementData description]);
}
- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals
{
NSLog( #"I retrieved CONNECTED peripherals");
}
-(void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals{
NSLog(#"This is it!");
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
NSString *messtoshow;
switch (central.state) {
case CBCentralManagerStateUnknown:
{
messtoshow=#"State unknown, update imminent.";
break;
}
case CBCentralManagerStateResetting:
{
messtoshow=#"The connection with the system service was momentarily lost, update imminent.";
break;
}
case CBCentralManagerStateUnsupported:
{
messtoshow=#"The platform doesn't support Bluetooth Low Energy";
break;
}
case CBCentralManagerStateUnauthorized:
{
messtoshow=#"The app is not authorized to use Bluetooth Low Energy";
break;
}
case CBCentralManagerStatePoweredOff:
{
messtoshow=#"Bluetooth is currently powered off.";
break;
}
case CBCentralManagerStatePoweredOn:
{
messtoshow=#"Bluetooth is currently powered on and available to use.";
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[_cbManager scanForPeripheralsWithServices:nil options:options];
break;
}
}
NSLog(#"%#", messtoshow);
}
#end

First you will need to configure your applications audio session to allow bluetooth connections that support audio. You can do this in, for example, your application delegates - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method. Make sure you link the AVFoundation Framework and import in headers that will use it.
#import <AVFoundation/AVFoundation.h>// place in .h
[self prepareAudioSession];// called from application didFinishLaunchingWithOptions
- (BOOL)prepareAudioSession {
// deactivate session
BOOL success = [[AVAudioSession sharedInstance] setActive:NO error: nil];
if (!success) {
NSLog(#"deactivationError");
}
// set audio session category AVAudioSessionCategoryPlayAndRecord options AVAudioSessionCategoryOptionAllowBluetooth
success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];
if (!success) {
NSLog(#"setCategoryError");
}
// activate audio session
success = [[AVAudioSession sharedInstance] setActive:YES error: nil];
if (!success) {
NSLog(#"activationError");
}
return success;
}
Every application has an audio session singleton that you can configure. The sessions category and mode (in this example I did not set the mode so it reverts to the default mode) declare your applications intentions as to how you would like audio routing to be handled. It follows an important rule of last in wins. This means that if the user plugs in a headset or in this case a bluetooth device that is a hands free peripheral (HFP) the system will automatically route the audio to the headset or bluetooth device. The users physical actions are used to determine audio routing. However if you wish to give the user a list of available routes Apple recommend using MPVolumeView class.
An example for adding MPVolumeView could be put in a UIViewController subclasses viewDidLoad method.
#import <MediaPlayer/MediaPlayer.h> // place in .h
// prefered way using MPVolumeView for user selecting audio routes
self.view.backgroundColor = [UIColor clearColor];
CGRect frameForMPVV = CGRectMake(50.0, 50.0, 100.0, 100.0);
MPVolumeView *routeView = [[MPVolumeView alloc] initWithFrame:frameForMPVV];
[routeView setShowsVolumeSlider:NO];
[routeView setShowsRouteButton:YES];
[self.view addSubview: routeView];
As of iOS 7 you can get all inputs like this
// portDesc.portType could be for example - BluetoothHFP, MicrophoneBuiltIn, MicrophoneWired
NSArray *availInputs = [[AVAudioSession sharedInstance] availableInputs];
int count = [availInputs count];
for (int k = 0; k < count; k++) {
AVAudioSessionPortDescription *portDesc = [availInputs objectAtIndex:k];
NSLog(#"input%i port type %#", k+1, portDesc.portType);
NSLog(#"input%i port name %#", k+1, portDesc.portName);
}
The portType you would be interested in is "BluetoothHFP". The portName property typically is the manufacturer/model which is what you would show to the user. (I've checked this with a non-LE bluetooth Motorola dinosaur and it works)
Because of the last in wins rule you will need to observe these two notifications (iOS 7 included). One to handle interruptions (such as phone calls or an alarm) and the second to be notified of route changes. Route change notifications is the one related to this question.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myInterruptionSelector:)
name:AVAudioSessionInterruptionNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myRouteChangeSelector:)
name:AVAudioSessionRouteChangeNotification
object:nil];
For iOS 6.x you could read the currentRoute property of AVAudioSession inside the myRouteChange: selector to get the new route, as this will get called when a headset or bluetooth device is connected.
- (void)myRouteChangeSelector:(NSNotification*)notification {
AVAudioSessionRouteDescription *currentRoute = [[AVAudioSession sharedInstance] currentRoute];
NSArray *inputsForRoute = currentRoute.inputs;
NSArray *outputsForRoute = currentRoute.outputs;
AVAudioSessionPortDescription *outPortDesc = [outputsForRoute objectAtIndex:0];
NSLog(#"current outport type %#", outPortDesc.portType);
AVAudioSessionPortDescription *inPortDesc = [inputsForRoute objectAtIndex:0];
NSLog(#"current inPort type %#", inPortDesc.portType);
}
Any iOS version < 6.0 you'll need the 'now deprecated' AudioSessionServices class. This class is a C api that instead of notifications it allows you to add property listeners.
I'll finish on this note - YOU DONT ALWAYS GET WHAT YOU WANT from the system. There are interruption handling notifications to observe and lots of error checking needed. I think this is a really good question and I hope this sheds some light on what it is you are trying to achieve.

Swift version
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: .allowBluetooth)
try AVAudioSession.sharedInstance().setActive(true)
} catch {}
let availableInputs = AVAudioSession.sharedInstance().availableInputs

Related

How to stop Avaudiosession from deactivating?

I want to play a song through speaker while being able to receive a video call using Quickblox.
My audio rates are getting messed up.
And also a bigger a problem is when the call ends quickblox framework sets audio session to deactivated state. i.e
[Avaudiosession sharedInstance]setActive:NO....
How do I stop this from happening?
Or is there a way the above mentioned scenario can be handled.??
I have read through google for a month now and still didn't find any suitable answers or any guidelines.
Can anyone help me with this problem/??
First, to allow AVAudioSession to work with other AVAudioSessions, you'll need to set up the option AVAudioSessionCategoryOptionMixWithOthers when initializing it:
NSError *sessionError = NULL;
BOOL success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:&sessionError];
if(!success) {
NSLog(#"Error setting category Audio Session: %#", [sessionError localizedDescription]);
}
To handle interruptions (a call, alarm, etc..), you should set up an observer for interruptions to the NSNotificationCenter where you'll be able to handle the activation/deactivation of the AVAudioSession if necessary:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleAudioSessionInterruption:)
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
The NSNotification will carry the Type of Interruption and Key:
- (void)handleAudioSessionInterruption:(NSNotification*)notification {
NSNumber *interruptionType = [[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey];
NSNumber *interruptionOption = [[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey];
switch (interruptionType.unsignedIntegerValue) {
case AVAudioSessionInterruptionTypeBegan:{
// • Audio has stopped, already inactive
// • Change state of UI, etc., to reflect non-playing state
NSLog(#"AVAudioSessionInterruptionTypeBegan");
} break;
case AVAudioSessionInterruptionTypeEnded:{
// • Make session active
// • Update user interface
// • AVAudioSessionInterruptionOptionShouldResume option
NSLog(#"AVAudioSessionInterruptionTypeEnded");
if (interruptionOption.unsignedIntegerValue == AVAudioSessionInterruptionOptionShouldResume) {
// Here you should continue playback.
}
} break;
default:
break;
}
}

AVRCP Profile for Bluetooth- iPhone [duplicate]

Okay, I'm working on a fun project that has a hurdle where I need to enable Bluetooth audio support for my iOS app.
The hurdle I'm at is that I simply can't even begin to get a list of connected Bluetooth audio devices. Even though my iPhone 5S recognizes my earpiece (a ~3 - 4 year old LG HBM-230, to be precise) and plays audio through it for phone calls, BOTH External Accessory and CoreBluetooth are giving me nothing useful when I query both.
I'm basing my own code off questions & answers I found for both the CoreBluetooth and External Accessory frameworks.
When my code simply tries to "scanForPeripheralsWithServices:nil" for any Bluetooth devices which Settings->Bluetooth say are visible and connected, the below code simply is NOT coming up with a single hit beyond the "CBCentralManagerStatePoweredOn" message in the console.
And this line in my code (with a valid EAAccessoryManager instance)
NSArray * connectedDevices = [self.eAAccessoryManager connectedAccessories];
also comes back with a nil array.
What could I be doing wrong?
B.T.W., I've made this code available as a GitHub project.
#implementation BluetoothManager
+ (BluetoothManager *)sharedInstance
{
static dispatch_once_t pred = 0;
__strong static id _bluetoothMGR = nil;
dispatch_once(&pred, ^{
_bluetoothMGR = [[BluetoothManager alloc] init];
});
return _bluetoothMGR;
}
- (id)init
{
self = [super init];
if(self)
{
dispatch_queue_t centralQueue = dispatch_queue_create("com.yo.mycentral", DISPATCH_QUEUE_SERIAL);
// whether we try this on a queue of "nil" (the main queue) or this separate thread, still not getting results
self.cbManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:nil];
}
return self;
}
// this would hit.... if I instantiated this in a storyboard of XIB file
- (void)awakeFromNib
{
if(!self.cbManager)
self.cbManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
NSLog(#"hey I found %#",[advertisementData description]);
}
- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals
{
NSLog( #"I retrieved CONNECTED peripherals");
}
-(void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals{
NSLog(#"This is it!");
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
NSString *messtoshow;
switch (central.state) {
case CBCentralManagerStateUnknown:
{
messtoshow=#"State unknown, update imminent.";
break;
}
case CBCentralManagerStateResetting:
{
messtoshow=#"The connection with the system service was momentarily lost, update imminent.";
break;
}
case CBCentralManagerStateUnsupported:
{
messtoshow=#"The platform doesn't support Bluetooth Low Energy";
break;
}
case CBCentralManagerStateUnauthorized:
{
messtoshow=#"The app is not authorized to use Bluetooth Low Energy";
break;
}
case CBCentralManagerStatePoweredOff:
{
messtoshow=#"Bluetooth is currently powered off.";
break;
}
case CBCentralManagerStatePoweredOn:
{
messtoshow=#"Bluetooth is currently powered on and available to use.";
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[_cbManager scanForPeripheralsWithServices:nil options:options];
break;
}
}
NSLog(#"%#", messtoshow);
}
#end
First you will need to configure your applications audio session to allow bluetooth connections that support audio. You can do this in, for example, your application delegates - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method. Make sure you link the AVFoundation Framework and import in headers that will use it.
#import <AVFoundation/AVFoundation.h>// place in .h
[self prepareAudioSession];// called from application didFinishLaunchingWithOptions
- (BOOL)prepareAudioSession {
// deactivate session
BOOL success = [[AVAudioSession sharedInstance] setActive:NO error: nil];
if (!success) {
NSLog(#"deactivationError");
}
// set audio session category AVAudioSessionCategoryPlayAndRecord options AVAudioSessionCategoryOptionAllowBluetooth
success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];
if (!success) {
NSLog(#"setCategoryError");
}
// activate audio session
success = [[AVAudioSession sharedInstance] setActive:YES error: nil];
if (!success) {
NSLog(#"activationError");
}
return success;
}
Every application has an audio session singleton that you can configure. The sessions category and mode (in this example I did not set the mode so it reverts to the default mode) declare your applications intentions as to how you would like audio routing to be handled. It follows an important rule of last in wins. This means that if the user plugs in a headset or in this case a bluetooth device that is a hands free peripheral (HFP) the system will automatically route the audio to the headset or bluetooth device. The users physical actions are used to determine audio routing. However if you wish to give the user a list of available routes Apple recommend using MPVolumeView class.
An example for adding MPVolumeView could be put in a UIViewController subclasses viewDidLoad method.
#import <MediaPlayer/MediaPlayer.h> // place in .h
// prefered way using MPVolumeView for user selecting audio routes
self.view.backgroundColor = [UIColor clearColor];
CGRect frameForMPVV = CGRectMake(50.0, 50.0, 100.0, 100.0);
MPVolumeView *routeView = [[MPVolumeView alloc] initWithFrame:frameForMPVV];
[routeView setShowsVolumeSlider:NO];
[routeView setShowsRouteButton:YES];
[self.view addSubview: routeView];
As of iOS 7 you can get all inputs like this
// portDesc.portType could be for example - BluetoothHFP, MicrophoneBuiltIn, MicrophoneWired
NSArray *availInputs = [[AVAudioSession sharedInstance] availableInputs];
int count = [availInputs count];
for (int k = 0; k < count; k++) {
AVAudioSessionPortDescription *portDesc = [availInputs objectAtIndex:k];
NSLog(#"input%i port type %#", k+1, portDesc.portType);
NSLog(#"input%i port name %#", k+1, portDesc.portName);
}
The portType you would be interested in is "BluetoothHFP". The portName property typically is the manufacturer/model which is what you would show to the user. (I've checked this with a non-LE bluetooth Motorola dinosaur and it works)
Because of the last in wins rule you will need to observe these two notifications (iOS 7 included). One to handle interruptions (such as phone calls or an alarm) and the second to be notified of route changes. Route change notifications is the one related to this question.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myInterruptionSelector:)
name:AVAudioSessionInterruptionNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myRouteChangeSelector:)
name:AVAudioSessionRouteChangeNotification
object:nil];
For iOS 6.x you could read the currentRoute property of AVAudioSession inside the myRouteChange: selector to get the new route, as this will get called when a headset or bluetooth device is connected.
- (void)myRouteChangeSelector:(NSNotification*)notification {
AVAudioSessionRouteDescription *currentRoute = [[AVAudioSession sharedInstance] currentRoute];
NSArray *inputsForRoute = currentRoute.inputs;
NSArray *outputsForRoute = currentRoute.outputs;
AVAudioSessionPortDescription *outPortDesc = [outputsForRoute objectAtIndex:0];
NSLog(#"current outport type %#", outPortDesc.portType);
AVAudioSessionPortDescription *inPortDesc = [inputsForRoute objectAtIndex:0];
NSLog(#"current inPort type %#", inPortDesc.portType);
}
Any iOS version < 6.0 you'll need the 'now deprecated' AudioSessionServices class. This class is a C api that instead of notifications it allows you to add property listeners.
I'll finish on this note - YOU DONT ALWAYS GET WHAT YOU WANT from the system. There are interruption handling notifications to observe and lots of error checking needed. I think this is a really good question and I hope this sheds some light on what it is you are trying to achieve.
Swift version
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: .allowBluetooth)
try AVAudioSession.sharedInstance().setActive(true)
} catch {}
let availableInputs = AVAudioSession.sharedInstance().availableInputs

Unable to restore bluetooth state on iOS

I have been trying for weeks to figure out how state preservation and restoration works for Core Bluetooth in iOS, but I am completely lost.
So far I have done the following:
Added a Restoration Identifier to my CBCentralManager
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:#{CBCentralManagerOptionRestoreIdentifierKey:#"myCentralManager"}];
Which should make my application able to call the delegate method:
centralManager:(CBCentralManager *)central
willRestoreState:(NSDictionary *)dict;
In here I am trying to reconnect to my peripheral, if I had a connection prior to the app being suspended, otherwise I will try to scan for my peripheral again, like so:
if (dict[CBCentralManagerRestoredStatePeripheralsKey]) {
for (CBPeripheral *currentPeripheral in peripherals) {
peripheral = currentPeripheral;
}
// Connect to peripheral
}
else{
// Scan for UUID
}
Am I doing this right, am I close, or is this completely wrong?
It works for me now.
I initialize my central manager, just like I did originally, and then in the willRestoreState method, I run the following code:
NSArray* peripherals = [central retrieveConnectedPeripheralsWithServices:[NSArray arrayWithObject:UUID]];
if ([peripherals count] > 0) {
_discoveredPeripheral = [peripherals objectAtIndex:0];
_discoveredPeripheral.delegate= self;
[central connectPeripheral:_discoveredPeripheral options:nil];
} else {
[self scan];
}

Bluetooth LE Device scan in background from iOS

I am working on to scan BLE in Background mode.
Issue is not working in Background scan. Its working very fine in Foreground mode.
Below is few code lines.
dispatch_queue_t centralQueue = dispatch_queue_create("com.XXXXX.BLEback", DISPATCH_QUEUE_SERIAL);// or however you want to create your dispatch_queue_t
manager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:nil];
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state == CBCentralManagerStatePoweredOn) {
[self startScan];
}
if (![self supportLEHardware])
{
#throw ([NSError errorWithDomain:#"Bluetooth LE not supported"
code:999
userInfo:nil]);
}
}
- (void)startScan
{
NSDictionary * options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:false] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
[manager scanForPeripheralsWithServices:nil options:options];
}
here i am passing nil as a services.
I receive log in Devices section in Xcode. But not in application.
Notice>: (Error) Discovered unknown type for scan: {
kCBAdvDataChannel = 37;
kCBAdvDataIsConnectable = 1;
kCBAdvDataManufacturerData = <00003962 6708f4c1 00000000 00d02b00 20d03300 20d03300 20>;
kCBAdvDataWSaturated = 0;
kCBAdvDataWlanRSSI = 0;
}, -51, puck type: 57
You cannot scan for nil services in the background - you must specify the service(s) that you are interested in. From the documentation
Apps that have specified the bluetooth-central background mode are
allowed to scan while in the background. That said, they must
explicitly scan for one or more services by specifying them in the
serviceUUIDs parameter.
For your app to continue to receive Bluetooth updates in the background, you need to add a UIBackgroundModes entry to your Info.plist and include the value bluetooth-central in the list.

Core Bluetooth State Restoration

I am working on an app that reacts on disconnects of peripherals and I am now trying to adopt the ne state preservation and restoration introduced in iOS 7.
I did everything like the documentation says, means:
I added the background mode for centrals.
I always instantiate my central manager with the same unique
identifier.
I implemented the centralManager:willRestoreState: method.
When my App moves to background I kill it in the AppDelegate callback with an kill(getpid(), SIGKILL);. (Core Bluetooth State Preservation and Restoration Not Working, Can't relaunch app into background)
When I now disconnect a peripheral by removing the battery my app is being waked up as expected and launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey] contains the correct identifier BUT the centralManager:willRestoreState: was not called.
Only if I disconnect another peripheral this method gets called.
This is how I have it:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSArray *peripheralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothPeripheralsKey];
if (peripheralManagerIdentifiers) {
// We've restored, so create the _manager on the main queue
_manager = [[CBPeripheralManager alloc] initWithDelegate:self
queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
options:#{CBPeripheralManagerOptionRestoreIdentifierKey:#"YourUniqueIdentifier"}];
} else {
// Not restored so just create as normal
manager = [[CBPeripheralManager alloc] initWithDelegate:self
queue:nil
options:#{CBPeripheralManagerOptionRestoreIdentifierKey:#"YourUniqueIdentifier"}];
}
return YES;
}
And then:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
willRestoreState:(NSDictionary *)dict
{
// This is the advertisement data that was being advertised when the app was terminated by iOS
_advertisementData = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey];
NSArray *services = dict[CBPeripheralManagerRestoredStateServicesKey];
// Loop through the services, I only have one service but if you have more you'll need to check against the UUID strings of each
for (CBMutableService *service in services) {
_primaryService = service;
// Loop through the characteristics
for (CBMutableCharacteristic *characteristic in _primaryService.characteristics) {
if ([characteristic.UUID.UUIDString isEqualToString:CHARACTERISTIC_UUID]) {
_primaryCharacteristic = characteristic;
NSArray *subscribedCentrals = characteristic.subscribedCentrals;
// Loop through all centrals that were subscribed when the app was terminated by iOS
for (CBCentral *central in subscribedCentrals) {
// Add them to an array
[_centrals addObject:central];
}
}
}
}
}

Resources