(iOS) How do I check an iPhone's paired Bluetooth devices? - iphone

I want to use CoreBluetooth (as my app must be eligible for the App Store) to check all of the currently paired and connected Bluetooth devices. This should not require any Bluetooth scanning, right? I just want to see what the system is paired with. If I can't do that, a scan is the second option.
What I'm trying is not working. It says that Bluetooth is not powered on and crashes, but the CBCentralManager's state is on! Any ideas on how to fix this, or am I totally off track?
All of this in ViewController for now:
- (void)viewDidLoad{
[super viewDidLoad];
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
[self.centralManager retrieveConnectedPeripherals]; //makes the system call didRetrieveConnectedPeripherals
}
- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals{
NSLog(#"didRetrieveConnectedPeripherals called");
for (CBPeripheral *a in peripherals){
NSLog(a.name); //just log the name for now to see if it recognized it
} //but it never ends up logging anything, and I have a BT keyboard paired/connected with the iPhone 5
} //and I get an error at some point, <CBConcreteCentralManager: 0x71ab020> is not powered on
- (void)centralManagerDidUpdateState:(CBCentralManager *)manager{
if ([manager state] == CBCentralManagerStatePoweredOff) NSLog(#"CBCentralManagerStatePoweredOff");
if ([manager state] == CBCentralManagerStatePoweredOn) NSLog(#"CBCentralManagerStatePoweredOn"); //this is what gets logged when I run it on an iPhone 5
if ([manager state] == CBCentralManagerStateResetting) NSLog(#"CBCentralManagerStateResetting");
if ([manager state] == CBCentralManagerStateUnauthorized) NSLog(#"CBCentralManagerStateUnauthorized");
if ([manager state] == CBCentralManagerStateUnknown) NSLog(#"CBCentralManagerStateUnknown");
if ([manager state] == CBCentralManagerStateUnsupported) NSLog(#"CBCentralManagerStateUnsupported");
}

I've been working on this also, and hopefully some of what I've learned will help.
Couple of things:
1) You're likely getting the "Bluetooth not powered on" error because you're calling [self.central retrieveConnectedPeripherals] immediately after initializing the CBCentralManager. You need to give the CBCentralManager enough time to connect to the bluetooth hardware.
Try this instead,
- (void)viewDidLoad{
[super viewDidLoad];
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)manager {
switch (manager.state) {
case CBCentralManagerStatePoweredOn:
NSLog(#"CBCentral Manager powered on");
[self.centralManager retrieveConnectedPeripherals];
break;
case CBCentralManagerStatePoweredOff:
...etc.
}
Also, be aware that CBCentralManager works for iOS devices with Bluetooth 4.0. Currently, Bluetooth 4 is installed on iPhone 4S/5, iPod 4, and iPad 3/4/mini. So, be aware that your App will not work on iPad 1/2, iPhone 2/3, and iPod 2/3.
Still figuring this out, but it is also possible that you may not see your keyboard because it is not a Bluetooth 4 device. Let me know what you find out.

Related

Still can't get CoreBluetooth to work in a simple name discovery app

I'm trying to make a simple application that will scan for nearby Bluetooth devices and list their names as they are discovered. I'm using CoreBluetooth in accordance with every guide I've found, including Apple's guide here
However, it never works. I put an iPhone 4S in discoverable mode next to the iPhone 5 running the app, and it never discovers it. I also tried a Bluetooth-enabled car, but I don't know if it has BLE. What am I doing wrong? Here is the essence of my code, in ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[activity stopAnimating]; isScanning = NO; //activity is a GUI activity wheel
centralManager = [[CBCentralManager alloc] initWithDelegate: self queue: nil];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
int state = central.state;
[self log: [NSString stringWithFormat: #"CBCentralManagerDidUpdateState: %d", state]];
//[self log] just NSLogs the message and adds it to a text view for the user to see.
if (state!=CBCentralManagerStatePoweredOn){
[self log: #"Error! Bluetooth not powered on!"]; //I never get this error.
}
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
[self log: [NSString stringWithFormat: #"Peripheral found with CoreBluetooth: %#", peripheral.name]];
//And I never see any of these "peripheral found" messages.
}
- (IBAction)scanButton:(id)sender {
if (!isScanning){
[activity startAnimating];
isScanning = YES;
[centralManager scanForPeripheralsWithServices:nil options:nil];
[self log: #"Scanning started."];
}
else{
[activity stopAnimating];
isScanning = NO;
[centralManager stopScan];
[self log: #"Scanning stopped."];
}
}
Thanks for any suggestions.
I found an answer here: Can't seem to get core bluetooth to work
I need an iOS device in peripheral mode or a BLE peripheral. Very annoying because basically no peripherals use BLE.
It works with my brother's iPhone 4S running a free app from the App Store called LightBlue. The app lets you put the device in peripheral mode... it's kind of the developer to put out a nice testing app like this.

Headset Button Press not recognized when in secondary views of iOS application

I have an iOS application that streams music using RadioKit SDK. The audio plays fine while switching between different views of a tab bar controller, however when a headset is plugged in and the user is viewing one of the secondary tab views (there are 5 total, 1 primary for when the app launches, and 4 others) if they try to hit the play/pause button the app does not recognize the action. If it's playing, it won't pause, if it's paused, it won't play. The app does, however, recognize volume changes from the headset.
This behavior is consistent whether the app is in view or if in background or if the device is locked.
I've done some heavy searching and can't seem to figure this out. Help is appreciated, thank you!
iOS version 6.1.3
iOS SDK version 6.1
xCode version 4.6.3
You want to listen to -(void)remoteControlReceivedWithEvent:(UIEvent *)event
Make a base UIViewController, let's call it BaseViewController and add the following :
-(void)remoteControlReceivedWithEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl) {
switch(event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
break;
case UIEventSubtypeRemoteControlPlay:
break;
case UIEventSubtypeRemoteControlPause:
break;
case UIEventSubtypeRemoteControlStop:
break;
default:
break;
}
}
else{
[super remoteControlReceivedWithEvent:event];
}
}
Also you want to include the following, in the base class, so that you can actually receive any remote control events.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIApplication *application = [UIApplication sharedApplication];
if ([application respondsToSelector:#selector(beginReceivingRemoteControlEvents)]) {
[application beginReceivingRemoteControlEvents];
}
[self becomeFirstResponder];
}
And finally have all the UIViewControllers be subclass of the BaseViewController.

How to check if sim card is installed or not

I am developing an app which has call and message functionality , i want to check if sim card is installed or not coz i am facing problem with messaging as it gives alerts for " Message Sent Successful"
Please help me out.
There might be different ways but one way is by using MFMessageComposeViewController class to see if you can send the text message. If you can then sim is available otherwise not.
if ([MFMessageComposeViewController canSendText]) {
NSLog(#"SIM Available");
} else {
NSLog(#"no SIM card installed");
}
In cases you have iMessage available then this might return you true, you could also check if you can make a call, you might want to use CTTelephonyNetworkInfo for that purpose.
You can also check using like this.... First read this doc
http://developer.apple.com/library/ios/#DOCUMENTATION/NetworkingInternet/Reference/CTCarrier/Reference/Reference.html#//apple_ref/doc/uid/TP40009596-CH1-SW1
NSString *_code = [[[CTCarrier alloc] init] mobileCountryCode];
The value for this property is nil if any of the following apply:
The device is in Airplane mode.
There is no SIM card in the device.
The device is outside of cellular service range.
First you have to be sure that device is iPhone (not iPod or iPad) then check if device can make call or not, just like this............
if([[UIDevice currentDevice].model isEqualToString:#"iPhone"])
{
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"tel:123456"]])
{
NSLog(#"Device can make call or send message");
}
else
{
NSLog(#"Device can not make call or send message");
}
}
else
{
NSLog(#"Device can not make call or send message");
}
Hope it will help you........

iPhone does not discover Services on a Bluetooth LE tag on reconnection

I am working on a Bluetooth LE application for iOS. I am using the Core Bluetooth framework within iOS to handle all communications.
Question & Description:
When I use a single tag, despite numerous connections and disconnections, the single tag connects seamlessly and the phone discovers it services.
Also, when multiple Bluetooth LE tags connect for the first time, they connect seamlessly and the phone discovers their services.
When the tags get disconnected and then reconnect to the phone, the tags connect fine. But one of the two tags (either one) does not seem to advertise its services. i.e when the app is open and the tag reconnects, the DiscoverServices method does not call the didDiscoverServices delegate.
Why is this happening only when connection with multiple devices takes place.
I have set the peripheral.delegate correctly. I have tried everything, including doing repeated re-connect, repeated DiscoverServices calls to the tag. Nothing seems to work.
How can I re-connect to multiple tags to the phone and still discover all services.
Please help
Thanks,
Manju
I had the same problem but realized that I wasn't setting the delegate to CBPeripheral after didConnectPeripheral is called.
- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(#"Peripheral Connected: %#", peripheral.name);
peripheral.delegate = self;
if (peripheral.services) {
[self peripheral:peripheral didDiscoverServices:nil];
} else {
[peripheral discoverServices:#[[CBUUID UUIDWithString:CUSTOM_UUID]]];
}
}
I was facing a similar issue with CoreBluetooth to connect to Bluetooth LE devices, in my case connecting to iOS devices (peripherals) from my Mac (central).
If I get you correctly, the pattern is quite consistent, the first time I run my Mac app for debuging, it always detected and connected to any bluetooth LE devices (peripherals), most importantly, it also discover their services/characteristics fine. The problem starts on the second run (for example, change some code, hit cmd-R to relaunch the debug). The central still detects peripherals and connects to them, but, it fails to discover any services/characteristics. In other words, the delegate peripheral:didDiscoverServices: and peripheral:didDiscoverCharacteristicsForService:error: never get called.
The solution after a lot of trial and errors, is surprisingly simple. It seems that CoreBluetooth caches services and characteristics for peripherals that are still connected, although locally it looks like it had been disconnected to the app, the peripheral still maintains a bluetooth connection to the system. For these type of connections, there is no need to (re)discover the services and characteristics, just access them directly from the peripheral object, check for nil to know if you should discover them. Also, as mentioned, since the peripheral is in a state that is in between connections, it is best to call cancelPeripheralConnection: right before attempting to connect. The gist of it as following, assuming we already discovered the peripheral to connects to:
-(void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
[central cancelPeripheralConnection:peripheral]; //IMPORTANT, to clear off any pending connections
[central connectPeripheral:peripheral options:nil];
}
-(void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
peripheral.delegate = self;
if(peripheral.services)
[self peripheral:peripheral didDiscoverServices:nil]; //already discovered services, DO NOT re-discover. Just pass along the peripheral.
else
[peripheral discoverServices:nil]; //yet to discover, normal path. Discover your services needed
}
-(void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
for(CBService* svc in peripheral.services)
{
if(svc.characteristics)
[self peripheral:peripheral didDiscoverCharacteristicsForService:svc error:nil]; //already discovered characteristic before, DO NOT do it again
else
[peripheral discoverCharacteristics:nil
forService:svc]; //need to discover characteristics
}
}
-(void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
for(CBCharacteristic* c in service.characteristics)
{
//Do some work with the characteristic...
}
}
This works well for me for a CBCentralManager in Mac app. Never tested it in iOS, but I assume it should be quite similar.
Turns out that there was command I was issuing to the device in the"didDiscovercharacteristicsForService" delegate method which was causing the connection instability.
If you are facing similar issues, I suggest you to let the delegate method complete without any intervention (of any kind) and pass the CBPeripheral to another function designed by you to pass any values / issue a command to the devices.
Thanks anyway Wilhemsen.
So the steps are as follows..
1> Search for Tag,
2> If in range, CONNECT to Tag
3> If Connected, call DISCOVER services method (do not interrupt)
4> IN DidDiscoverServices, call DISCOVER Characteristics Method
..
In DidDiscoverCharacteristics Method, wait until all Characteristics are discovered..
Then , at the end, call a function in your code that will do the necessary setup..
...
Code Sample
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
for (int i=0; i < peripheral.services.count; i++) {
CBService *s = [peripheral.services objectAtIndex:i];
printf("Fetching characteristics for service with UUID : %s\r\n",[self CBUUIDToString:s.UUID]);
[peripheral discoverCharacteristics:nil forService:s];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (!error)
{
CBService *s = [peripheral.services objectAtIndex:(peripheral.services.count - 1)];
if([self compareCBUUID:service.UUID UUID2:s.UUID])
{
// This is when the **Tag** is completely connected to the Phone and is in a mode where it can accept Instructions from Device well.
printf("Finished discovering characteristics");
// Now Do the Setup of the Tag
[self setupPeripheralForUse:peripheral];
}
}
else
{
printf("Characteristic discorvery unsuccessfull !\r\n");
}
}
-(void) setupPeripheralForUse:(CBPeripheral *)peripheral
{
// DO all your setup in this function. Separate Perpiheral Setup from the process that synchronizes the tag and the phone.
}
I've been having the same issue. it seems to occur about 1/3 the time in my case. I tried the solution provided by P.L. but I had no success on iOS. There are perhaps many moving pieces at work here which can contribute to this problem (bluetooth device firmware, CoreBluetooth, etc.) but I solved it by simply keeping track of the devices which are pending service/characteristic discovery in an NSMutableDictionary and using GCD to check if it had completed it's discovery in a reasonable amount of time and trying again if necessary. So something like this:
- (void)requestDiscoverServicesForPeripheral:(CBPeripheral *)peripheral
{
peripheral.delegate = self;
NSString *uuid =[self stringUUIDForUUID:peripheral.UUID];
NSLog(#"discovering %d services ", self.interestingServices.count);
[peripheral discoverServices:self.interestingServices];
[self.pendingConnectionDevices setObject:peripheral forKey:uuid];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if ([weakSelf.pendingConnectionDevices objectForKey:uuid]) {
//request discover services again
NSLog(#"services did not discover, requesting again!");
[weakSelf.btManager cancelPeripheralConnection:peripheral];
[weakSelf.btManager connectPeripheral:peripheral options:nil];
}
});
}
Then, when peripheral:didDiscoverServices:error calls back I remove it from my pendingConnectionDevices dictionary. This seems to work pretty well. I've seen it try to discover services up to 3 times before succeeding. I hope this helps.
Sometimes it's hardware issue. I just encountered a case that the hardware will enter a sleeping mode which is scannable, connectable, but not calling back at all for discoverServices.
Also there's a situation that happened to me when developing BLE all night and suddenly, the device become silence for discoverServices no matter what I did. Finally I found it will become normal if I reboot my iPhone5s.
And one day the bluetooth hardware engineer told me that the spec of BLE only says device must be connectable again within 30 seconds. So if I connect and disconnect over and over again, it may not working as expected... Which I still doubt it.
I was trying to apply all above answers but it wasn't working for me because I was missing CBPeripheralDelegate on class.
class BLEHandler : NSObject, CBCentralManagerDelegate, CBPeripheralDelegate{
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
print("Connected: \(peripheral.state == .connected ? "YES" : "NO")")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service: CBService in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service)
print(service)
}
}
}
I'm doing everything you say in your answer, but I still see this issue from time to time. My guess is Core Bluetooth gets into a weird state, probably due to a specific sequence of connection, subscription and disconnection.
The only way I've found to fix the problem is to restart the iOS device. This issue might be fixed in iOS 7.1 though.
My headache was caused by lack of strong reference to peripheral
So I've add the reference and the problem is gone
private var activePeripheral: CBPeripheral?
func connect(_ peripheral: CBPeripheral) {
disconnect(peripheral)
activePeripheral = peripheral
self.central.connect(peripheral, options: nil)
}

Is there any notification for detecting AirPlay in Objective-C?

I am using MPVolumeView for showing Airplay icon and it works fine.
But I need to show an animation when Airplay network comes, and hide that animation when airplay network hides.
Is there a notification that will let me know when Airplay starts and ends?
This is exactly what you're looking for - https://github.com/StevePotter/AirPlayDetector
It is a single class that provides a property to determine whether airplay devices are active. And a notification when availability changes.
Using it is simple. Like, to determine availability you write:
[AirPlayDetector defaultDetector].isAirPlayAvailable
Enjoy!
To be precise:
To check exactly for airplay with public API: NO
All you can do with public API is to check for available wireless routes, which includes airplay in it: (In simple case when you have a MPVolumeView instance hooked up somewhere to your view, you can just call volumeView.areWirelessRoutesAvailable;)
If you are curious how to check if exactly airplay is available with private API:
- (BOOL)isAirplayAvailable
{
Class MPAVRoutingController = NSClassFromString(#"MPAVRoutingController");
id routingController = [[MPAVRoutingController alloc] init];
NSArray* availableRoutes = [routingController performSelector:#selector(availableRoutes)];
for (id route in availableRoutes) {
NSDictionary* routeDescription = [route performSelector:#selector(avRouteDescription)];
if ([routeDescription[#"AVAudioRouteName"] isEqualToString:#"AirTunes"])
return true;
}
return false;
}
(And in fact MPVolumeView has an MPAVRoutingController instance as its ivar, so the -areWirelessRoutesAvailable is just an accessor exactly for [volumeView->_routingController wirelessDisplayRoutesAvailable])
Also AVAudioSession exposes currentRoute to you, so you do can check if airplay is active easily with:
- (BOOL)isAudioSessionUsingAirplayOutputRoute
{
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
AVAudioSessionRouteDescription* currentRoute = audioSession.currentRoute;
for (AVAudioSessionPortDescription* outputPort in currentRoute.outputs){
if ([outputPort.portType isEqualToString:AVAudioSessionPortAirPlay])
return true;
}
return false;
}
(the answer about AirPlayDetector doesn't guarantee that Airplay is available - all it does it checks the alpha value of MPVolumeView's routeSelection button, which will be shown in any case when wireless routes are available, bluetooth for example. It will do exactly the same as volumeView.areWirelessRoutesAvailable;)
There's a MPVolumeViewWirelessRoutesAvailableDidChangeNotification since iOS 7 you can register for.
It can be done much easier with ReactiveCocoa. Check it out:
MPVolumeView *myVolumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(0, 0, 180, 22)];
for (UIView *view in myVolumeView.subviews) {
if ([view isKindOfClass:[UIButton class]]) {
[[RACAbleWithStart(view, alpha) distinctUntilChanged] subscribeNext:^(id x) {
NSLog(#"airplay button visibility changed %#", x);
}];
[[RACAbleWithStart(view, frame) distinctUntilChanged] subscribeNext:^(id x) {
NSLog(#"airplay button connection changed %#", x);
}];
}
}
6 years later.
I think Sankar Siva did not ask for detecting, but for activating an airplay route.
I've upped #Alf because he placed me on the right direction, but he is not answering to the question.
MPVolumeViewWirelessRoutesAvailableDidChangeNotification fires when MPVolumeView detects a new route.
On the other hand, MPVolumeViewWirelessRouteActiveDidChangeNotification fires when a new route is taken, eg: when you select your Apple TV for example.
No need of private API.
If you want a notification here is the way to do it
[[NSNotificationCenter defaultCenter]
addObserver:self
selector: #selector(deviceChanged:)
name:AVAudioSessionRouteChangeNotification
object:[AVAudioSession sharedInstance]];
- (void)deviceChanged:(NSNotification *)sender {
NSLog(#"Enters here when connect or disconnect from Airplay");
}