GKSession - what if I have Bluetooth and Wi-Fi turned off? - iphone

I'm working on an iPhone app that will allow for peer-to-peer connections. From what I understand, I have the choice between using GKPeerPicker or the GKSession. I don't like the idea of using the PeerPicker because I want to show a custom interface, so I decided to go with GKSession, and hey, BONUS is that it also works over Wi-Fi, whereas the Peer Picker does not.
OK, so problem is... what if the user has both Bluetooth and Wi-Fi turned off? In the Peer Picker, there is a prompt to turn Bluetooth on w/o leaving the app. GKSession doesn't have it... but woah wait a second, it appears that I can't even check to see if Bluetooth is on or not programatically!
Carpe Cocoa claims no problem, just use the Delegate's session:didFailWithError: method. But, as it explains in the comments... that doesn't seem to work anymore! And in my experience, I concur.
Is there some other way to programmatically check if Bluetooth is on? Is this something that I should be leveraging Reachability for? Or is it just a bug that Apple has yet to fix?
To be more specific, I'm creating my session like this:
GKSession *aSession = [[GKSession alloc] initWithSessionID:nil
displayName:user.displayName
sessionMode:GKSessionModePeer];
self.gkSession = aSession;
[aSession release];
self.gkSession.delegate = self;
self.gkSession.available = YES;
[self.gkSession setDataReceiveHandler:self withContext:NULL];
The class implements the GKSessionDelegate, and I know that it's working because when I have bluetooth turned on, the delegate methods are called no problem. I've implemented them as such:
#pragma mark -
#pragma mark GKSessionDelegate methods
- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state {
if (GKPeerStateAvailable == state) {
[session connectToPeer:peerID withTimeout:10];
} else if (GKPeerStateConnected == state) {
// gets user
NSError *error = nil;
[session sendData:user.connectionData
toPeers:[NSArray arrayWithObjects:peerID,nil]
withDataMode:GKSendDataReliable error:&error];
if (error)
NSLog(#"%#",error);
}
}
- (void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID {
NSError *error = nil;
[session acceptConnectionFromPeer:peerID error:&error];
if (error)
NSLog(#"%#",error);
}
- (void)session:(GKSession *)session connectionWithPeerFailed:(NSString *)peerID withError:(NSError *)error {
NSLog(#"%#",error);
}
- (void)session:(GKSession *)session didFailWithError:(NSError *)error {
NSLog(#"%#",error);
}
None of the log statements are printed and I set breakpoints in each method, but none of them are hit when the user has both Bluetooth and Wi-Fi turned off. I was hoping that something would happen to trigger session:didFailWithError: so that I could prompt the user to turn on Bluetooth or connect to a Wi-Fi network.

Now in iOS 5, this can be achieved like so:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
NSMutableArray * discoveredPeripherals = [NSMutableArray new];
CBCentralManager * manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[manager scanForPeripheralsWithServices:discoveredPeripherals options:options];
[manager stopScan];
This requires that you import the CoreBluetooth framework in iOS 5. If bluetooth is off, the system will pop up an alert view which will offer the choice to turn bluetooth on. Otherwise, if it finds a peripheral it will call a corresponding delegate method, but if there is nothing in that implementation you don't need to worry about it.

I agree with Martin Gordon, but a workaround might be to use Apple's reachability.

Interesting point, have you tried testing it with Bluetooth OFF and the WiFi ON? I found out recently that although my program was calling this 'Bluetooth Unavailable' message, it wasn't in fact using Bluetooth AT ALL but was connecting over my WiFi network. I don't know of a way to force GKSession into a Bluetooth connection without using Apple's PeerPicker object, but the PeerPicker object does allow for people to make their own interfaces. What it doesn't seem to allow is connection types other than Peer, so if you want a Client/Server arrangement it's not going to be much help.
-Ash

You can switch on Blutooth programmatically, by using Apple's private API (i think BluetoothManger.h), but be careful , it will cause rejection in the Apple App Store push.

I second the notion of using Apple's reachability. As a bonus it's listed as one of the Apple App Store submission guidelines.
It's not that hard to implement either as much of the code needed is already written for you.
Slf provided a link to some source code using the Reachability class, additionally here's a link to Apple Dev's official reachability example.
However, make sure you are checking for connectability asynchronously.
I'm using it in my app and although it isn't the best solution at least it notifies the user that he/she needs to adjust the connection settings or that no networks exist.

You should use the same sessionID.

Related

Checking if Bluetooth is Disabled on iOS 5 without BluetoothManager

I have seen that in iOS 5, CoreBluetooth provides the capability to check if Bluetooth is disabled. From what I have seen of the documentation, it is clearly aimed at bluetooth peripheral use. However, I am attempting to check if bluetooth is on because I am using GameKit (GKPeerPickerController) that will search endlessly for bluetooth connections if it is not enabled, which is an issue.
I tried to do this like so:
CBCentralManager * manager = [[CBCentralManager alloc] init];
if (manager.state == CBCentralManagerStatePoweredOn ) {
//go ahead with GameKit
}
This does not work, and manager.state is always equal to null. What am I doing wrong here? Or alternatively, are there better ways to check the status of bluetooth on the iPhone?
EDIT: I don't want to call any private APIs because I will be submitting this app to the app store. I have edited my question title to clarify this.
Ok, I discovered that by doing this:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
NSMutableArray * discoveredPeripherals = [NSMutableArray new];
CBCentralManager * manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[manager scanForPeripheralsWithServices:discoveredPeripherals options:options];
[manager stopScan];
If bluetooth is off, the system will pop up an alert view which will offer the choice to turn bluetooth on. Otherwise, if it finds a peripheral it will call a corresponding delegate method, but if there is nothing in that implementation you don't need to worry about it.
You can find the answer of your question by this link. Check it out.
Edited
Have you checked out the Game Kit Framework reference?
According to Apple:
Game Kit offers your applications the ability to create Bluetooth
connections between two devices.
Edited
Then try this project. https://github.com/sergiomtzlosa/MultiContactsSelector-ios

Check if Bluetooth is Enabled?

I just want to do a simple check of whether bluetooth is enabled on the device or not.
I don't want to change the status from inside an app (or at all), use private API's, jailbreak a device, or do anything that would cause Apple to reject an app.
All I want is to know whether bluetooth is turned on or not.
Can anyone shed any light on this? Is there any Apple-allowed way to do this?
I am fully aware, after reading countless posts and documentation that Apple is very restrictive when it comes to Bluetooth (among other things).
If you are only able to contribute to this question with a link to documentation and/or some snide remark about learning objective-c, reading documentation, etc., then please don't respond.
The only way I've ever found to do this is with private frameworks (like Bluetooth Manager, for one) that are only useful for Jailbroken apps... and Apple will reject any app using a private framework. I believe it's even against their ToS to do anything with bluetooth, so you're out of luck there.
There seems to be an answer here - Using Core bluetooth framework
However, that answer will only work for iOS 5.0 and up. I haven't tested this myself, but will return and add feedback if I find that it works.
You can now check this using the CBCentralManager in iOS 7 and initialize it with the CBCentralManagerOptionShowPowerAlertKey option set.
The CBCentralManagerOptionShowPowerAlertKey key, which can be passed to the initWithDelegate:queue:options: method on CBCentralManager which will cause iOS to start the Central Manager & not prompt the user to enable bluetooth.
Posted here: http://chrismaddern.com/determine-whether-bluetooth-is-enabled-on-ios-passively/
Unfortunately not, the SDK does not expose Bluetooth methods.
There may be a way to do it by using undocumented methods, however we all know the problem there.
For iOS9+, you can check my answer here.
#import <CoreBluetooth/CoreBluetooth.h>
#interface ShopVC () <CBCentralManagerDelegate>
#property (nonatomic, strong) CBCentralManager *bluetoothManager;
#end
#implementation ShopVC
- (void)viewDidLoad {
[super viewDidLoad];
if(!self.bluetoothManager)
{
NSDictionary *options = #{CBCentralManagerOptionShowPowerAlertKey: #NO};
self.bluetoothManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:options];
}
}
#pragma mark - CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
NSString *stateString = nil;
switch(self.bluetoothManager.state)
{
case CBCentralManagerStateResetting: stateString = #"The connection with the system service was momentarily lost, update imminent."; break;
case CBCentralManagerStateUnsupported: stateString = #"The platform doesn't support Bluetooth Low Energy."; break;
case CBCentralManagerStateUnauthorized: stateString = #"The app is not authorized to use Bluetooth Low Energy."; break;
case CBCentralManagerStatePoweredOff: stateString = #"Bluetooth is currently powered off."; break;
case CBCentralManagerStatePoweredOn: stateString = #"Bluetooth is currently powered on and available to use."; break;
default: stateString = #"State unknown, update imminent."; break;
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Bluetooth state"
message:stateString
delegate:nil
cancelButtonTitle:#"ok" otherButtonTitles: nil];
[alert show];
}

iPhone networking game

I already know how to make a online game using wi-Fi and bluetooth, but now i'm trying to implement a simple game over the internet(like a tic-tac-toe), but i'm kind lost.
I tried GET/POST, but i have no idea in how to notify a player that's waiting to move, and how to preserve the game state.
Do i have to open a socket on the server side, and connect from within the app?
I already made a extensive search, but i can only find about bluetooth and wifi, and that's not what i need.
Thanks everybody!
I would recommend this library: http://code.google.com/p/cocoaasyncsocket/
For the server side, you could use something like node.js or Python Twisted to open socket connections.
Sample code from one of my own projects. This sets up a socket that just reads from the host every 10 seconds. Your game will be a little different:
- (void) createSocket {
aisSocket = [[AsyncSocket alloc]initWithDelegate:self];
NSError *error;
[aisSocket connectToHost:myServerString
onPort:myServerPort
error:&error];
}
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock {
NSLog(#"socket will connect");
return YES;
}
- (void) readData:(NSNotification*)note {
[[note.userInfo objectForKey:#"sock"] readDataWithTimeout:100 tag:0];
}
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
NSLog(#"The socket is connected: %d", [aisSocket isConnected]);
NSDictionary *dict = [NSDictionary dictionaryWithObject:sock forKey:#"sock"];
NSTimer *timer = [[NSTimer scheduledTimerWithTimeInterval:(10)
target:self
selector:#selector(readData:)
userInfo:dict
repeats:YES]retain];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
Go read Apple's WiTap Sample code, it should match what you are trying to do.
Also consult GameKit Programming Guide to know how to make multiple devices discover themselves then communicate with each other in a game (or anything else)

getting operator details in iphone

How to get the currently using operator details (like Airtel or Idea or etc..)of iPhone.
Is it possible to get those details or is there any way to identify which operator currently we are using.I am developing an application which is based on the operator, if user changes his SIM(Operator) then the app shouldn't work, it has to work for that particular operator.
CTCarrier should have the info you need in it.
Edit: CTTelephonyNetworkInfo responds to a user switching SIMs mid-session, and provides an instance of CTCarrier for you.
Sample code (in your app delegate):
#import <CoreTelephony/CoreTelephony.h>
static NSString *requiredCarrier = #"Verizon"; // example carrier
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
CTTelephonyNetworkInfo *myNetworkInfo = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier *myCarrier = [myNetworkInfo subscriberCellularProvider];
if(![[myCarrier carrierName] isEqualToString:requiredCarrier])
{
// lock the app, possibly terminate the app after displaying a UIAlertView
// informing the user of the network-lock.
}
// ...
return YES;
}
Note the following advisory on the properties of CTCarrier (I would recommend not doing a permanent one-way lock if a nil carrier is read):
"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."
Of the properties of CTCarrier to validate on, I would recommend carrierName specifically as it doesn't change when the user is roaming so the app will still work as long as the SIM is tied to your desired operator.
Just a quick note in case someone is looking for this.
I have noticed from playing with the CTCarrier API that "nil" is returned to any of its properties only on the emulator. On the device it returns #"" (blank string) for some reason! Checking agains nil failed ont eh device but checking equality with #"" worked!
You can use the Core Telephony framework to achieve this. In particular CTCarrier and the carrierName property. View the documentation here: http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Reference/CTCarrier/Reference/Reference.html%23//apple_ref/doc/uid/TP40009596

iOS 4: Remote controls for background audio

I'm currently attempting to set up background audio for an app I'm developing for iOS 4. The app doesn't have a dedicated music player viewController, however, unlike other background audio apps such as Pandora, which makes the task a bit more confusing.
I've set the appropriate Info.plist settings correctly and have an AVAudioPlayer object in my app delegate which is accessible from everywhere. When the user plays a song, I replace the AVAudioPlayer with a new one initialized with the song and play it. This all works great, except now I have no idea how to go about supporting remote control events.
Based on Apple's documentation, I have this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
switch(event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
if([iPhoneAppDelegate backgroundAudioPlayer].playing)
[iPhoneAppDelegate pauseBackgroundAudioPlayer];
else
[iPhoneAppDelegate playBackgroundAudioPlayer];
break;
}
}
The thing is, where do I put this? Apple's documentation seems to suggest this should go in some view controller somewhere, but my app has lots of view controllers and navigation controllers. Wherever I try to put this, for some reason tapping the Toggle Play/Pause button in the multitasking tray remote controls either causes the song to just pause for a moment and then unpause, or somehow causes the song to play twice.
The documentation examples are a bit misleading, but there is no need to subclass anything anywhere. The correct place to put remoteControlReceivedWithEvent: is in the application delegate, as it remains in the responder chain regardless of whether the app is in the foreground or not. Also the begin/end receiving remote control events should be based on whether you actually need the events, not on the visibility of some random view.
I found a couple of solutions to receiving global remote control events on the Apple Developer Forums after a bit of searching.
One way is to subclass UIWindow and override its remoteControlReceivedWithEvent:.
The second, perhaps nicer way is to subclass UIApplication and override sendEvent:. That way, you can intercept all the remote control events and handle them there globally, and not have any other responders handle them later in the responder chain.
- (void)sendEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl) {
// Handle event
}
else
[super sendEvent:event];
}
The second method didn't work for me, sendEvent was never called. However the first method worked just nicely (subclassing UIWindow).
I struggled with this one for a while and none of the answers above worked. The bug in my code, and I hope that it will help someone reading this, was that I had the AudioSession set to mix with others. You want to be the foreground audio player to get Remote Control events. Check to see if you have INCORRECT code like this:
[[AVAudioSession sharedInstance] setDelegate: self];
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: nil];
UInt32 doSetProperty = 0;
AudioSessionSetProperty (
kAudioSessionProperty_OverrideCategoryMixWithOthers,
sizeof (doSetProperty),
&doSetProperty
);
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
And remove the AudioSessionSetProperty, or change doSetProperty to 1.
No need to subclass Window or forward events. Simply handle it from your main view controller. See the Audio Mixer (MixerHost) example for details.
http://developer.apple.com/LIBRARY/IOS/#samplecode/MixerHost/Listings/Classes_MixerHostViewController_m.html
Documentation explains it very well.
https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Remote-ControlEvents/Remote-ControlEvents.html
One thing that seems to influence this behavior is any category options you set for your AVAudioSession using setCategory:withOptions:error: instead of just setCategory:error:. In particular, from trial and error, it appears that if you set AVAudioSessionCategoryOptionMixWithOthers you will not get remote control events; the now playing controls will still control the iPod app. If you set AVAudioSessionCategoryOptionDuckOthers you will get remote control events, but it seems like there may be some ambiguity regarding which app is controlled. Setting the categoryOptions to 0 or just calling setCategory:error: works best.