Multipeer connectivity with one device having app running in background - ios

I would like to connect 2 devices using multipeer connectivity framework where one of those devices is running the app in the background, just like Firechat does (I can't confirm this is working, I have installed it on an iPhone 5S and 4, but they just can't find each other - but I have read somewhere this works).
What's the best way to achieve this?
I'm using the following two methods from an example code:
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName{
_peerID = [[MCPeerID alloc] initWithDisplayName:displayName];
_session = [[MCSession alloc] initWithPeer:_peerID];
_session.delegate = self;
}
-(void)setupMCBrowser{
_browser = [[MCBrowserViewController alloc] initWithServiceType:#"chat-files" session:_session];
}
-(void)advertiseSelf:(BOOL)shouldAdvertise{
if (shouldAdvertise) {
_advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:#"chat-files"
discoveryInfo:nil
session:_session];
[_advertiser start];
}
else{
[_advertiser stop];
_advertiser = nil;
}
}
When I'm running the app in the foreground, it finds the other device flawlessly and also connects. But if I put one of the apps into background, the background-app-device is no longer visible.
I have already read this: Can I have a multipeer connectivity session run in the background? - But I can't believe this is not possible without any workaround.
FYI, my app is also using background location updates, if that's relevant...
Thanks!
EDIT:
Is there some other way of doing this?? Basically I just want to send a message to the other device (to the one who's app is running in the background) - since Multipeer connectivity doesn't work in the background, can I connect via bluetooth directly for example?

Apple have confirmed that "Multipeer Connectivity does not function in the background". The reason is that if your app is suspended then its socket resources can be reclaimed and everything falls apart.
However, it is possible to monitor iBeacons when in the background. Essentially, you set your app up to monitor proximity to a beacon with a specific id. Then, your app that is in the foreground becomes a beacon with that same id. This causes the following app delegate method to be called on the app in the background:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
UILocalNotification *notification = [[UILocalNotification alloc] init];
if(state == CLRegionStateInside) {
notification.alertBody = NSLocalizedString(#"A nearby app would like to connect", #"");
} else {
return;
}
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
Here you issue a local notification that, when tapped on by the user, will bring your app into the foreground, at which point you can start advertising for a peer-to-peer connection.

Related

IBeacon region monitoring not work consistently across devices

When testing with a simple app to test beacon region monitoring I seem to get very inconsistent results depending on the device (not the device model, the specific device). The problem is that I don't receive the CLRegionStateInside state on the region after requestStateForRegion and didEnterRegion does not get called at all on these device. startRangingBeaconsinRegion: works fine but to conserve power and processing it is recommended to only start ranging when the didEnterRegion: method gets called. I tested this on 6 devices and it works on half on them (iPhone 5's) and does't work on one iPhone 5, one 5S and one 4S.
The beacons I use are the kontakt.io beacons.
This is the code to setup the region monitoring
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BEACON_UUID];
CLBeaconRegion *region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
identifier:#"regionIdentifier"];
region.notifyOnEntry = YES;
region.notifyOnExit = YES;
region.notifyEntryStateOnDisplay = YES;
[self.locationManager startMonitoringForRegion:region];
[self.locationManager requestStateForRegion:region];
//If I enable this line, ranging starts on all devices
// [self.locationManager startRangingBeaconsInRegion:region];
I Found the problem. Apparently to use iBeacons in the way that is described in the documents users are required to have the Background Refresh setting enabled in the Settings. To check for this setting I use the following snippet:
if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusAvailable) {
NSLog(#"Background updates are available for the app.");
}else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusDenied)
{
NSLog(#"The user explicitly disabled background behavior for this app or for the whole system.");
}else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusRestricted)
{
NSLog(#"Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.");
}
Found in this answer: Detecting user settings for Background App Refresh in iOS 7
Monitoring may not work for several reasons. One being that App Background Refresh is disabled to save your battery. Another one is neglecting to ensure you have setup the correct App Capabilities.
If this doesn't work there is a great post you can read that details all of the items to troubleshoot.
iBeacon StartMonitoringForRegion Doesn’t Work

CLBeaconRegion notifyEntryStateOnDisplay and UILocalNotifications

I am trying to get a local notification to fire any time I turn on my phone and I am inside a specific region. This works as expected but each time I turn my device on I get a new notification. If I just leave the existing notification it can get pushed down to the bottom of the notification list. If I cancel the existing notification and create a new one I get a weird animation. Is there a way to either:
Update an existing UILocalNotification that has already been delivered to push it to the top.
Somehow get notified when the lock screen goes away and cancel the notification then?
Here is my existing code:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if ([region isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion *beaconRegion = (CLBeaconRegion*)region;
UILocalNotification *existingNotification = self.beaconNotification;
switch (state) {
case CLRegionStateInside:
self.beaconNotification = [[UILocalNotification alloc] init];
self.beaconNotification.alertBody = #"You're inside the region";
[[UIApplication sharedApplication] presentLocalNotificationNow:self.beaconNotification];
if (existingNotification) {
[[UIApplication sharedApplication] cancelLocalNotification:existingNotification];
}
break;
case CLRegionStateOutside:
self.beaconNotification = [[UILocalNotification alloc] init];
self.beaconNotification.alertBody = #"You're outside the region";
[[UIApplication sharedApplication] cancelAllLocalNotifications];
break;
default:
break;
}
}
}
There are definitely ways to detect if the phone is locked/unlocked Is there a way to check if the iOS device is locked/unlocked?
For further notifications, I suggest you look at this list:http://iphonedevwiki.net/index.php/SpringBoard.app/Notifications
It contains the com.apple.springboard.deviceWillShutDown notification that is called when the phone is shutting down. I just tested it with this code and seems to work, however, the app gets killed immediately and the XCode session terminates so I'm not sure how useful this is for a real application.
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
NULL,
shutdownCallback,
CFSTR("com.apple.springboard.deviceWillShutDown"),
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
void shutdownCallback (CFNotificationCenterRef center, void *observer,
CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
NSLog(#"Shutting down");
}
Considering the weird animation, if you don't like it, then report to Apple. Only they can fix it. I don't think you should update the notification (if this is possible at all). Simply push a new one, and either remove the old or don't bother with it. This is what most applications do and users generally have no issues with it. It serves as a kind of history, too.

CLLocationManager, didEnterRegion and UILocalNotification

So after much trial and error and reading Apples documents and SO threads I thought I had my didEnterRegion working properly.
This is what I finished up with...
- (void)locationManager:(LocationManager *)locationManager didEnterRegion:(CLRegion *)region{
NSLog(#"Location manager did enter region called");
[self.locationManager stopUpdatingLocation];
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
UILocalNotification *localNotification = [[UILocalNotification alloc]init];
localNotification.alertBody = #"You are about to arrive";
localNotification.alertAction = #"Open";
localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication]applicationIconBadgeNumber]+1;
[[UIApplication sharedApplication]presentLocalNotificationNow:localNotification];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"App Running in foreground notification fired");
[self setupAlarmTriggeredView];
//vibrate device
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
});
}
}
As you can see it does a simple check to see if the app is active, if not it sets up a UILocalNotification which it presents immediately, if it is it just vibrates and changes the view.
When testing on the simulator using a GPX file to move the location over the boundary, both scenarios work perfectly. However when testing the app out and about, when the app is in the background the notification doesn't seem to fire until you wake the device. ie. you can cross the boundary and nothing happens, then when you unlock the device, boom, it vibrates and the view is changed accordingly. (if the app is not foremost when you unlock this doesn't happen until you re-launch the app).
Should I be doing this setup in appDidFinishLaunchingWithOptions? In my testing both that method and didRecieveLocalNotification: in the app delegate don't get called until 'after' the notification has been fired AND the user has re-launched the app by actioning the notification (at which point you can check the for the launch key in the options array), this doesn't seem to be of any use for the initial firing of the notification as part of didEnterRegion. At the moment I have no code in either of these methods.
As far as I'm aware I don't need to be doing any background location updates for didEnterRegion, this is handled automatically by iOS.

Ranging Beacons only works when app running?

I am having difficulties getting this to work for when the app is not running. I have locationManager:didRangeBeacons:inRegion: implemented and it is called when the app is running in the foreground or background, however it doesn't seem to do anything when I quit the app and lock the screen. The location services icon goes away and I never know that I entered a beacon range. Should the LocalNotification still work?
I have Location updates and Uses Bluetooth LE accessories selected in Background Modes (XCode 5) I didn't think I needed them.
Any help greatly appreciated.
-(void)watchForEvents { // this is called from application:didFinishLaunchingWithOptions
id class = NSClassFromString(#"CLBeaconRegion");
if (!class) {
return;
}
CLBeaconRegion * rflBeacon = [[CLBeaconRegion alloc] initWithProximityUUID:kBeaconUUID identifier:kBeaconString];
rflBeacon.notifyOnEntry = YES;
rflBeacon.notifyOnExit = NO;
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startRangingBeaconsInRegion:rflBeacon];
[self.locationManager startMonitoringForRegion:rflBeacon];
}
-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
if (beacons.count == 0 || eventRanged) { // breakpoint set here for testing
return;
}
eventRanged = YES;
if (backgroundMode) { // this is set in the EnterBackground/Foreground delegate calls
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = [NSString stringWithFormat:#"Welcome to the %# event.",region.identifier];
notification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
// normal processing here...
}
Monitoring can launch an app that isn't running. Ranging cannot.
The key to having monitoring launch your app is to set this poorly documented flag on your CLBeaconRegion: region.notifyEntryStateOnDisplay = YES;
This can launch your app on a region transition even after completely rebooting your phone. But there are a couple of caveats:
Your app launches into the background only for a few seconds. (Try adding NSLog statements to applicationDidEnterBackground and other methods in your AppDelegate to see what is going on.)
iOS can take its own sweet time to decide you entered a CLBeaconRegion. I have seen it take up to four minutes.
As far as ranging goes, even though you can't have ranging wake up your app, you can make your app do both monitoring and ranging simultaneously. If monitoring wakes up your app and puts it into the background for a few seconds, ranging callbacks start up immediately. This gives you a chance to do any quick ranging actions while your app is still running.
EDIT: Further investigation proves that notifyEntryStateOnDisplay has no effect on background monitoring, so the above should work regardless of whether you have this flag. See this detailed explanation and discussion of delays you may experience
Code for iOS 9 to range beacons in the background, by using Location Updates:
Open Project Settings -> Capabilities -> Background Modes -> Toggle Location Updates and Uses Bluetooth LE accessories to ON.
Create a CLLocationManager, request Always monitoring authorization (don't forget to add the Application does not run in background to NO and NSLocationAlwaysUsageDescription in the app's info.plist) and set the following properties:
locationManager!.delegate = self
locationManager!.pausesLocationUpdatesAutomatically = false
locationManager!.allowsBackgroundLocationUpdates = true
Start ranging for beacons and monitoring region:
locationManager!.startMonitoringForRegion(yourBeaconRegion)
locationManager!.startRangingBeaconsInRegion(yourBeaconRegion)
locationManager!.startUpdatingLocation()
// Optionally for notifications
UIApplication.sharedApplication().registerUserNotificationSettings(
UIUserNotificationSettings(forTypes: .Alert, categories: nil))
Implement the CLLocationManagerDelegate and in your didEnterRegion send both startRangingBeaconsInRegion() and startUpdatingLocation() messages (optionally send the notification as well) and set the stopRangingBeaconsInRegion() and stopUpdatingLocation() in didExitRegion
Be aware that this solution works but it is not recommended by Apple due to battery consumption and customer privacy!
More here: https://community.estimote.com/hc/en-us/articles/203914068-Is-it-possible-to-use-beacon-ranging-in-the-background-
Here is the process you need to follow to range in background:
For any CLBeaconRegion always keep monitoring on, in background or foreground and keep notifyEntryStateOnDisplay = YES
notifyEntryStateOnDisplay calls locationManager:didDetermineState:forRegion: in background, so implement this delegate call...
...like this:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
if (state == CLRegionStateInside) {
//Start Ranging
[manager startRangingBeaconsInRegion:region];
}
else{
//Stop Ranging
[manager stopRangingBeaconsInRegion:region];
}
}
I hope this helps.
You are doing two separate operations here - 'ranging' beacons and monitoring for a region. You can monitor for a region in the background, but not range beacons.
Therefore, your implementation of locationManager:didRangeBeacons:inRegion: won't get called in the background. Instead, your call to startMonitoringForRegion will result in one / some of the following methods being called:
– locationManager:didEnterRegion:
– locationManager:didExitRegion:
– locationManager:didDetermineState:forRegion:
These will get called in the background. You can at that point trigger a local notification, as in your original code.
Your app should currently wake up if you're just wanting to be notified when you enter a beacon region. The only background restriction I know of concerns actually hosting an iBeacon on an iOS device. In that case, the app would need to be physically open in the foreground. For that situation, you'd be better off just doing the straight CoreBluetooth CBPeripheralManager implementation. That way you'd have some advertising abilities in the background.

will CBCentralManager ,CBService delegate methods be called in background mode in iOS6

I am using Apple core bluetooth example .The peripheral is running in foreground in one iphone device.I am running cbcentral client application in one device.It is pairing well when both application in foreground condition.My need is when I run client cbcentral client in background , that delegate methods are not called in which I have mentioned local notification .the notification is not coming in background mode.
Can I use NSOperation for running bluetooth delegate methods as we do NSUrlConnection? Will it work in latest iOS version? I checked it, but it is not working.
The code:
-(void) peripheral:(CBPeripheral *)aPeripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
..............
...............
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
localNotif.fireDate = [itemDate dateByAddingTimeInterval:-(minutesBefore*60)];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.alertBody = #"hi";
localNotif.alertAction = NSLocalizedString(#"View Details", nil);
localNotif.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
..............
}
I believe what you are looking for are the core bluetooth UIBackgroundModes here.
Also, you may want to look at Core Bluetooth and backgrounding: Detection of a device and triggering an action, even after being days in background mode? and What exactly can CoreBluetooth applications do whilst in the background?
The core bluetooth background modes work in iOS 5 or later.

Resources