How to handle socket connection's events when app is in background? - iphone

I want use the following function even when app is in background?
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
{
case NSStreamEventHasBytesAvailable:
{ NSLog(#"Event:NSStreamEventHasBytesAvailable");
if (theStream == _inputStream) {
NSLog(#"NSStreamEventHasBytesAvailable: on Input Stream");
uint8_t buffer[1024];
int len;
while ([_inputStream hasBytesAvailable]) {
len = [_inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
if (nil != output) {
NSLog(#"server said: %#", output);
// to get local notification I am calling below method.
[self scheduleNotification];
}
}
}
}
break;
}
The above code is working done in foreGround. I have made all the change given in apple document to the run the app in the background mode- voip.
What should i write in AppDelegate method?
- (void)applicationDidEnterBackground:(UIApplication *)application
{
}
How to get the stream:handleEvent called in background?

I was dealing with similiar problem a while ago. Few important things to keep in mind:
background "voip" functionality only works on device - don't use simulator to test it
you will probably (tested) got rejected if your app registers as a voip app and isn't really voip app
So if this is not a voip app you might actually want to use remote notifications to alert user directly rather than showing local notification. I guess this is the only way for your app to pass App Store validation.
Anyway, two links here on SO helped you might find helpful:
How can an iOS app keep a TCP connection alive indefinitely while in the background?
I ended up using voip (as you do) and playing silent audio loop as suggested here - it worked. Not sure if
this silent audio loop is still neccessary.
What happens to TCP and UDP (with multicast) connection when an iOS Application did enter background
Make sure you read Tips for Developing a VoIP App and Technical Note TN2277:Networking and Multitasking

Use this code to keep your app alive in ios
var backgroundUpdateTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)
func endBackgroundUpdateTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
}
func applicationWillResignActive(_ application: UIApplication) {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func applicationDidBecomeActive(_ application: UIApplication) {
self.endBackgroundUpdateTask()
}
i hope you will get success by this help

Related

How to handle silent push notification in ionic cordova application?

I'm following ionic documentation to create ionic push notifications. This works fine when the app is in active state. I need to run a function when the app receive a push notification while the app is in background.
$ionicPush.register({
canShowAlert: false, //Should new pushes show an alert on your screen?
canSetBadge: true, //Should new pushes be allowed to update app icon badges?
canPlaySound: false, //Should notifications be allowed to play a sound?
canRunActionsOnWake: true, // Whether to run auto actions outside the app,
onNotification: function(notification) {
// Called for each notification.
}
});
The issue i'm facing is onNotification callback function does not firing when the app is in background. How do I achieve that using ionic push notification API?
In this case, onNotification will be only fired when you click on notification in tray, which open applications. If notification is in android tray, it means notification has not been seen, because of which it will never reach application unless you click on it.
I suspect this patch may solve your problem:
https://github.com/phonegap-build/PushPlugin/issues/288
(quoted here in case it disappears)
We have managed to get this plugin to respond to silent push notifications which in turn call javascript functions, all while running in background mode. I thought we would share our solution as it seems like a lot of people are wanting this feature.
In AppDelegate+notification.m, change didReceiveRemoteNotification:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
NSLog(#"didReceiveNotification");
// Get application state for iOS4.x+ devices, otherwise assume active
UIApplicationState appState = UIApplicationStateActive;
if ([application respondsToSelector:#selector(applicationState)]) {
appState = application.applicationState;
}
PushPlugin *pushHandler = [self getCommandInstance:#"PushPlugin"];
pushHandler.notificationMessage = userInfo;
if (appState == UIApplicationStateActive)
pushHandler.isInline = YES;
else
pushHandler.isInline = NO;
[pushHandler notificationReceived];
handler(UIBackgroundFetchResultNewData);
}
In Pushplugin.m, change notificationReceived:
- (void)notificationReceived {
NSLog(#"Notification received");
if (notificationMessage && self.callback)
{
NSMutableString *jsonStr = [NSMutableString stringWithString:#"{"];
[self parseDictionary:notificationMessage intoJSON:jsonStr];
if (isInline)
{
[jsonStr appendFormat:#"foreground:\"%d\"", 1];
isInline = NO;
}
else
[jsonStr appendFormat:#"foreground:\"%d\"", 0];
[jsonStr appendString:#"}"];
NSLog(#"Msg: %#", jsonStr);
NSString * jsCallBack = [NSString stringWithFormat:#"%#(%#);", self.callback, jsonStr];
[self.webView stringByEvaluatingJavaScriptFromString:jsCallBack];
//get javascript function to fire in background mode
CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:jsonStr];
[self.commandDelegate sendPluginResult:commandResult callbackId:self.callbackId];
self.notificationMessage = nil;
}
}
Assuming you are willing to adapt it to work with the
current non-deprecated plugin:
https://github.com/phonegap/phonegap-plugin-push

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)
}

didReceiveRemoteNotification when in background

These kind of question has been asked a number of times but i have some specific situation going on.
When my application is active and I receive a PUSH message i'm successfully able to parse the custom payloads and such.
However when my application is in the background and the PUSH arrives the user has to click on the 'View/Open' button in order to get the didReceiveRemoteNotification called and the didFinishLaunchingWithOptions is called after that.
I need to have my application decide if they have to prompt the user with an UIAlert when in the background or suppress the push message based on some local settings.
Any help would be appreciated,
You app needs to handle all the possible push notification delivery states:
Your app was just launched
Your app was just brought from background to foreground
Your app was already running in the foreground
You do not get to choose at delivery time what presentation method is used to present the push notification, that is encoded in the notification itself (optional alert, badge number, sound). But since you presumably are in control of both the app and the payload of the push notification, you can specify in the payload whether or not there was an alert view and message already presented to the user. Only in the case of the app is already running in the foreground do you know that the user did not just launch your app through an alert or regularly from the home screen.
You can tell whether your app was just brought to the foreground or not in didReceiveRemoteNotification using this bit of code:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
if ( application.applicationState == UIApplicationStateActive )
// app was already in the foreground
else
// app was just brought from background to foreground
...
}
You have to do several things in order to manage the received push notification when the app is in background.
First, in your server side, you have to set {"aps":{"content-available" : 1... / $body['aps']['content-available'] =1; in the push notification payload.
Second, in your Xcode project, yo have to habilitate "remote notifications". It is made by going to the project's target -> capabilities, then enable the capabilities switch, and check the remote notifications checkbox.
Third, instead of using didReceiveRemoteNotification, you have to call application:didReceiveRemoteNotification:fetchCompletionHandler:, this will allow you to do the tasks that you want in the background, at the moment of receiving the notification:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
if(application.applicationState == UIApplicationStateInactive) {
NSLog(#"Inactive - the user has tapped in the notification when app was closed or in background");
//do some tasks
[self manageRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}
else if (application.applicationState == UIApplicationStateBackground) {
NSLog(#"application Background - notification has arrived when app was in background");
NSString* contentAvailable = [NSString stringWithFormat:#"%#", [[userInfo valueForKey:#"aps"] valueForKey:#"content-available"]];
if([contentAvailable isEqualToString:#"1"]) {
// do tasks
[self manageRemoteNotification:userInfo];
NSLog(#"content-available is equal to 1");
completionHandler(UIBackgroundFetchResultNewData);
}
}
else {
NSLog(#"application Active - notication has arrived while app was opened");
//Show an in-app banner
//do tasks
[self manageRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}
}
Finally, you have to add this notification type: UIRemoteNotificationTypeNewsstandContentAvailability to the notifications settings when you set it.
Apart from this, if your app was closed when the notification arrived, you have to manage this in didFinishLaunchingWithOptions , and just if the user taps on the push notification: The way of do it is:
if (launchOptions != nil)
{
NSDictionary *dictionary = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (dictionary != nil)
{
NSLog(#"Launched from push notification: %#", dictionary);
[self manageRemoteNotification:dictionary];
}
}
launchOptions is != nil when you launch the app by tapping on the push notification, if you access it by tapping on the icon, launchOptions will be == nil.
I hope it will be useful. Here it is explained by Apple.
Pass content-available = 1 with your payload, and will invoke didReceiveRemoteNotification even in background. e.g.
{
"alert" : "",
"badge" : "0",
"content-available" : "1",
"sound" : ""
}
One thing to keep in mind, when your push message arrives at the user's iPhone and they click "cancel", except for the icon badge number (they'll be taken care of by the OS), there would be no way for your in-the-background app to know about this push event and take any further actions.
Word of warning
I think your app logic is basing behavior on custom data in your push notification. This is not what push notifications are intended for. What you should alternatively do on didbecomeactive in your application is just ask your server for the data you need and was sending as payload anyway, and rely on that instead of your payload.
Because the documentation also states that as best practice. Because Apple does not guarantee your push notification from being received 100% of the times anyway.
Important: Delivery of notifications is a “best effort”, not
guaranteed. It is not intended to deliver data to your app, only to
notify the user that there is new data available.
However, if you want to have some indication of whether for instance the badge has been changed without relying on a user opening the app by clicking on the badge you could something like this:
A. you add a (correct) badge number to the payload of the push notification sent by your server. It for instance could look like this:
{
"aps" : {
"alert" : "You got your emails.",
"badge" : 9
}
}
B. you keep track of that badge number persistently in your app, for instance by storing it in NSUserDefaults.
Then in applicationDidBecomeActive can compare the applicationIconBadgeNumber property of UIApplication with your previously stored badge count and see if it has been increased or decreased and do some updates based on that.
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSNumber *badgecount = [[NSUserDefaults standardUserDefaults] objectForKey:#"badgecount"];
if (!badgecount) badgecount = #(0);
if ([UIApplication sharedApplication].applicationIconBadgeNumber != [badgecount integerValue]) {
//store the new badge count
badgecount = [NSNumber numberWithInteger:[UIApplication sharedApplication].applicationIconBadgeNumber];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:badgecount forKey:#"badgecount"];
[defaults synchronize];
// do some stuff here because it's different
}
}
As of recent iOS - I think 8 - if you've got remote notifications enabled as a background mode, one trick is to track whether you're entering the foreground as a flag.
#interface AppDelegate ()
#property (assign, atomic, getter=isEnteringForeground) BOOL enteringForeground;
#end
- (void) applicationWillEnterForeground: (UIApplication *) application
{
self.enteringForeground = YES;
}
- (void) applicationDidBecomeActive: (UIApplication *) application
{
self.enteringForeground = NO;
}
- (void) application: (UIApplication *) application didReceiveRemoteNotification: (NSDictionary *) userInfo fetchCompletionHandler: (void (^) (UIBackgroundFetchResult)) completionHandler
{
const BOOL launchedFromBackground = !(application.applicationState == UIApplicationStateActive);
const BOOL enteringForeground = self.enteringForeground;
if (launchedFromBackground && enteringForeground) {
// The user clicked a push while the app was in the BG
}
}

how to resume recording after interruption occured in iphone?

I am working on an audio recorder application, it is working perfectly fine.
But I'm stuck with the problem of interruption. When a call comes,
- (void)audioRecorderBeginInterruption:(AVAudioRecorder *)recorder
then this method is called and the recording is paused.
And if the user rejects the call:
- (void)audioRecorderEndInterruption:(AVAudioRecorder *)recorder
Then here I want to resume the recording from the point where it was interrupted.
But when I call the record method again, the recording starts with a new file.
The problem is solved!
I have re-written the recording code to avoid this problem. I used AudioQueues, basically the backend code is the same of SpeakHere application with some minor changes. Provided two more apis in it:
-(void)resume
{
AudioQueueStart(queueObject, NULL);
}
-(void)pause
{
AudioQueuePause(queueObject);
}
In AudioRecorder class. The basic purpose being to avoid the recording setup which is done in record method.
Setup the interrupt callback and then use this pause and resume methods appropriately in the callback. Also take care to set active the audio session based on whether your application needs it or not.
Hope this helps someone out there.
EDIT:
Audio interrupt listener callback:
void interruptionListenerCallback (void *inUserData, UInt32 interruptionState)
{
if (interruptionState == kAudioSessionBeginInterruption)
{
[self.audioRecorder pause];
}
else if (interruptionState == kAudioSessionEndInterruption)
{
// if the interruption was removed, and the app had been recording, resume recording
[self.audioRecorder resume];
}
}
Listening for audio interruption:
AudioSessionInitialize(NULL, NULL, interruptionListenerCallback, self);
The OS is probably stopping the AVAudioRecorder during the interruption. You can either present the two or more files as a single recording to the user, or you can use AudioQueue to write your code for handling interruptions (as well as saving audio data to a file).
I tried to do this a while ago and came to the conclusion that it couldn't be done properly; the operating system itself won't let you do it. Maybe that's changed in 3.1.2, but when I tried in 3.0 it just wouldn't work.
To handle the interruption, you have to use AVAudioSessionDelegate methodes instead of AVAudioRecorderDelegate methods.This is the code sample for handling interruption:
/*=================================================================
Interruption Handling Method during Recording
==================================================================*/
- (void) beginInterruption
{
if(self.recorder)
{
//Method to handle UI
}
}
- (void) endInterruption
{
NSError *err = noErr;
[[AVAudioSession sharedInstance] setActive: YES error: &err];
if(err != noErr)
{
NSLog([err description]);
}
//method to handle UI
}
First method automatically deactivate the audio session.So,in second method,you have to reactivate the audio session.First method will pause the recording and when interruption ends you can resume with second method.I have tried this on version 3.0 and upper version.

Lock Unlock events iphone

How can I detect lock/unlock events on the iPhone? Assuming it's only possible for jailbroken devices, can you point me to the correct API?
By lock events, I mean showing or hiding the Lock Screen (which might need a password to unlock, or not).
You can use Darwin notifications, to listen for the events. From my testing on a jailbroken iOS 5.0.1 iPhone 4, I think that one of these events might be what you need:
com.apple.springboard.lockstate
com.apple.springboard.lockcomplete
Note: according to the poster's comments to a similar question I answered here, this should work on a non-jailbroken phone, too.
To use this, register for the event like this (this registers for just the first event above, but you can add an observer for lockcomplete, too):
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
(void*)self, // observer (can be NULL)
lockStateChanged, // callback
CFSTR("com.apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
where lockStateChanged is your event callback:
static void lockStateChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSLog(#"event received!");
if (observer != NULL) {
MyClass *this = (MyClass*)observer;
}
// you might try inspecting the `userInfo` dictionary, to see
// if it contains any useful info
if (userInfo != nil) {
CFShow(userInfo);
}
}
The lockstate event occurs when the device is locked and unlocked, but the lockcomplete event is only triggered when the device locks. Another way to determine whether the event is for a lock or unlock event is to use notify_get_state(). You'll get a different value for lock vs. unlock, as described here.
Round about answer:
Application will resign active gets called in all sorts of scenarios... and from all my testing, even if your application stays awake while backgrounded, there are no ways to determine that the screen is locked (CPU speed doesn't report, BUS speed remains the same, mach_time denom / numer doesn't change)...
However, it seems Apple does turn off the accelerometer when the device is locked... Enable iPhone accelerometer while screen is locked
(tested iOS4.2 on iPhone 4 has this behavior)
Thus...
In your application delegate:
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(#"STATUS - Application will Resign Active");
// Start checking the accelerometer (while we are in the background)
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1]; // Ping every second
_notActiveTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(deviceDidLock) userInfo:nil repeats:NO]; // 2 seconds for wiggle
}
//Deprecated in iOS5
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
NSLog(#"STATUS - Update from accelerometer");
[_notActiveTimer invalidate];
_notActiveTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(deviceDidLock) userInfo:nil repeats:NO];
}
- (void)deviceDidLock
{
NSLog(#"STATUS - Device locked!");
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
_notActiveTimer = nil;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"STATUS - Application did become active");
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
[_notActiveTimer invalidate];
_notActiveTimer = nil;
}
I know... It's kind of a hack, but it has worked like a charm for me so far. Please update if you see any issues that prevent this from working.
There is a prettier way of telling apart task switching and screen locking-originated applicationWillResignActive: callbacks which doesn't even involve undocumented features such as the accelerometer state.
When the app is moving to the background, the app delegate is first sent an applicationWillResignActive:, then an applicationDidEnterBackground:. When the app is interrupted by pressing the Lock button or by an incoming phone call, the latter method is not called. We can use this information to distinguish between the two scenarios.
Say you want to be called back in the screenLockActivated method if the screen gets locked. Here's the magic:
- (void)applicationWillResignActive:(UIApplication*)aApplication
{
[self performSelector:#selector(screenLockActivated)
withObject:nil
afterDelay:0];
}
- (void)applicationDidEnterBackground:(UIApplication*)aApplication
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)screenLockActivated
{
NSLog(#"yaay");
}
Explanation:
By default, we assume that every call to applicationWillResignActive: is because of an active->inactive state transition (as when locking the screen) but we generously let the system prove the contrary within a timeout (in this case, a single runloop cycle) by delaying the call to screenLockActivated. In case the screen gets locked, the system finishes the current runloop cycle without touching any other delegate methods. If, however, this is an active->background state transition, it also invokes applicationDidEnterBackground: before the end of the cycle, which allows us to simply cancel the previously scheduled request from there, thus preventing it from being called when it's not supposed to.
Enjoy!
As of the time of writing there are two fairly reliable ways to detect device locking:
Data Protection
By enabling the Data Protection entitlement your app can subscribe to the applicationProtectedDataWillBecomeUnavailable: and applicationProtectedDataDidBecomeAvailable: notifications to determine with high probability when a device that uses passcode/TouchID Authentication is locked/unlocked. To determine if a device uses a passcode/TouchID LAContext can be queried.
Caveats: This method relies on the "protected data becoming unavailable" coinciding with the phone being locked. When the phone is using TouchID and the sleep/lock button is pressed then the phone is locked, protected data becomes unavailable, and a passcode will immediately be required to unlock it again. This means that protected data becoming unavailable essentially indicates that the phone has been locked. This is not necessarily true when someone is using just a passcode since they can set the "requires passcode" time to anywhere from immediately to something like 4 hours. In this case the phone will report being able to handle protected data but locking the phone will not result in protected data becoming unavailable for quite some time.
Lifecycle Timing
If your app is in the foreground there will be a noticeable change in time difference between the two lifecycle events UIApplicationWillResignActiveNotification and UIApplicationDidEnterBackgroundNotification depending on what triggers them.
(This was tested in iOS 10 and may change in future releases)
Pressing the home button results in a significant delay between the two (even when the Reduced Motion setting is enabled):
15:23:42.517 willResignActive
15:23:43.182 didEnterBackground
15:23:43.184 difference: 0.666346
Locking the device while the app is open creates a more trivial (<~0.2s) delay between the two events:
15:22:59.236 willResignActive
15:22:59.267 didEnterBackground
15:22:59.267 difference: 0.031404
in iOS 8, you lock the screen or push the home button, all of those make app push in background, but you don't know which operator result in this. My solution same with Nits007ak,use notify_register_dispatch to get state.
#import <notify.h>
int notify_token
notify_register_dispatch("com.apple.springboard.lockstate",
&notify_token,
dispatch_get_main_queue(),
^(int token)
{
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
if(state == 0) {
NSLog(#"unlock device");
} else {
NSLog(#"lock device");
}
}
);
As long as the app is running, in foreground or background. not suspend, you can get this event.
And you can use notify_token as parameter of notify_get_state to get current state anywhere, this is useful when you want know the state and the screen state don't change.
If passcode is set, you can use these event in AppDelegate
-(void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application
{
}
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application
{
}
Just import #import notify.h before using this code. enjoy!!
-(void)registerAppforDetectLockState {
int notify_token;
notify_register_dispatch("com.apple.springboard.lockstate", &notify_token,dispatch_get_main_queue(), ^(int token) {
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
if(state == 0) {
NSLog(#"unlock device");
} else {
NSLog(#"lock device");
}
NSLog(#"com.apple.springboard.lockstate = %llu", state);
UILocalNotification *notification = [[UILocalNotification alloc]init];
notification.repeatInterval = NSDayCalendarUnit;
[notification setAlertBody:#"Hello world!! I come becoz you lock/unlock your device :)"];
notification.alertAction = #"View";
notification.alertAction = #"Yes";
[notification setFireDate:[NSDate dateWithTimeIntervalSinceNow:1]];
notification.soundName = UILocalNotificationDefaultSoundName;
[notification setTimeZone:[NSTimeZone defaultTimeZone]];
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
});
}
From a lot of trial and error, discovered monitoring the blank screen, lock complete and lock state events gives a consistent lock screen indicator. You'll need to monitor a state transition.
// call back
void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
// notification comes in order of
// "com.apple.springboard.hasBlankedScreen" notification
// "com.apple.springboard.lockcomplete" notification only if locked
// "com.apple.springboard.lockstate" notification
AppDelegate *appDelegate = CFBridgingRelease(observer);
NSString *eventName = (__bridge NSString*)name;
NSLog(#"Darwin notification NAME = %#",name);
if([eventName isEqualToString:#"com.apple.springboard.hasBlankedScreen"])
{
NSLog(#"SCREEN BLANK");
appDelegate.bDeviceLocked = false; // clear
}
else if([eventName isEqualToString:#"com.apple.springboard.lockcomplete"])
{
NSLog(#"DEVICE LOCK");
appDelegate.bDeviceLocked = true; // set
}
else if([eventName isEqualToString:#"com.apple.springboard.lockstate"])
{
NSLog(#"LOCK STATUS CHANGE");
if(appDelegate.bDeviceLocked) // if a lock, is set
{
NSLog(#"DEVICE IS LOCKED");
}
else
{
NSLog(#"DEVICE IS UNLOCKED");
}
}
}
-(void)registerforDeviceLockNotif
{
// screen and lock notifications
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.hasBlankedScreen"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.lockcomplete"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
}
To have the screen lock indicators run in the background, you need to implement background processing calling the following upon app launching.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.backgroundTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
}];
[self registerforDeviceLockNotif];
}
If your app is running and the user locks the device your app delegate will receive a call to 'application Will Resign Active:'. If your app was running when locked, it will receive a call to 'application Did Become Active:' when the device is unlocked. But you get the same calls to your app if the user gets a phone call and then chooses to ignore it. You can't tell the difference as far as I know.
And if your app wasn't running at any of these times there is no way to be notified since your app isn't running.
The simplest way to get screen lock and unlock events are by adding event observers using NSNotificationCenter in your viewcontroller. I added the following observer in the viewdidload method. This is what i did:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationEnteredForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
Then I added the following selector to the viewcontroller. This selector will get called when the screen is unlocked.
- (void)applicationEnteredForeground:(NSNotification *)notification {
NSLog(#"Application Entered Foreground");
}
If you want to detect the event when screen gets locked, you can replace UIApplicationWillEnterForegroundNotification with UIApplicationDidEnterBackgroundNotification.