14

I wanted to detect two events :

  1. Device gets locked/unlocked.
  2. Device goes to sleep and the screen blackens.

First one I have been able to achieve here: Is there a way to check if the iOS device is locked/unlocked?

Now I want to detect the second event, is there any way to do it ?

Community
  • 1
  • 1
Rohit Kashyap
  • 906
  • 2
  • 8
  • 23

2 Answers2

22

You basically already have the solution, which I'm guessing you found from one of my recent answers :)

Use the com.apple.springboard.hasBlankedScreen event.

There are multiple events that occur when the screen blanks, but this one should suffice:

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
                                NULL, // observer
                                hasBlankedScreen, // callback
                                CFSTR("com.apple.springboard.hasBlankedScreen"), // event name
                                NULL, // object
                                CFNotificationSuspensionBehaviorDeliverImmediately);

where the callback is:

static void hasBlankedScreen(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
    NSString* notifyName = (__bridge NSString*)name;
    // this check should really only be necessary if you reuse this one callback method
    //  for multiple Darwin notification events
    if ([notifyName isEqualToString:@"com.apple.springboard.hasBlankedScreen"]) {
       NSLog(@"screen has either gone dark, or been turned back on!");
    }
}

Update: as @VictorRonin said in his comment below, it should be easy to keep track yourself whether the screen is currently on or off. That allows you to determine whether the hasBlankedScreen event is occurring when the screen goes on or off. For example, when your app starts, set a variable to indicate that the screen is on. Also, any time any UI interaction occurs (button pressed, etc.), you know the screen must currently be on. So, the next hasBlankedScreen you get should indicate that the screen is off.

Also, I want to make sure we're clear on the terminology. The device locks when the screen automatically darkens due to a timeout, or when the user manually presses the power button. This happens regardless of whether the user has a Passcode configured. At that time, you will see the com.apple.springboard.hasBlankedScreen and the com.apple.springboard.lockcomplete events.

When the screen turns back on, you will see com.apple.springboard.hasBlankedScreen once again. But, you will not see com.apple.springboard.lockstate until the user has actually unlocked the device with a swipe (and maybe a passcode).


Update 2:

There's yet another way to do this. You can use an alternate set of APIs to listen for this notification, and also get a state variable when the notification comes:

#import <notify.h>

int status = notify_register_dispatch("com.apple.springboard.hasBlankedScreen",
                                      &notifyToken,
                                      dispatch_get_main_queue(), ^(int t) {
                                          uint64_t state;
                                          int result = notify_get_state(notifyToken, &state);
                                          NSLog(@"lock state change = %llu", state);
                                          if (result != NOTIFY_STATUS_OK) {
                                              NSLog(@"notify_get_state() not returning NOTIFY_STATUS_OK");
                                          }
                                      });
if (status != NOTIFY_STATUS_OK) {
    NSLog(@"notify_register_dispatch() not returning NOTIFY_STATUS_OK");
}

and you will need to keep an ivar, or some other persistent variable, to store the notification token (do not just make this a local variable in the method that registers!)

int notifyToken;

You should see the state variable, obtained via notify_get_state(), toggle between 0 and 1, which will let you distinguish between screen on and off events.

Although this document is very old, it does list which notification events have an associated state that can be retrieved via notify_get_state().

Warning: see this related question for some complications with this last technique

Community
  • 1
  • 1
Nate
  • 30,589
  • 12
  • 76
  • 201
  • I have already tried this, turns out, this notification comes in both the scenarios, when device goes to sleep and when device awakes from the sleep.: Jan 16 18:48:53 unknown SpringBoard[15] : Posting 'com.apple.iokit.hid.displayStatus' notifyState=0 Jan 16 18:48:53 unknown Myapp[1921] : Darwin notification NAME = com.apple.springboard.hasBlankedScreen an 16 18:50:29 unknown SpringBoard[15] : Posting 'com.apple.iokit.hid.displayStatus' notifyState=1 Jan 16 18:50:29 unknown Myapp[1921] : Darwin notification NAME = com.apple.springboard.hasBlankedScreen – Rohit Kashyap Jan 16 '13 at 13:19
  • 1
    You can keep a state variable, whether a device is asleep now or awake. – Victor Ronin Jan 16 '13 at 16:31
  • Do I notice correctly that my app will not receive lockstate or lockcomplete notifications if it is already in the background when the lock action occurs? – Spencer Williams Jan 16 '13 at 23:14
  • @SpencerWilliams, not necessarily. If you just press Home to background your app, assuming your app .plist does not specify that it exits on suspend, then you will get the notifications. Now, as with anything, there are limits on how much time your app gets in the background, so you may have to fiddle with things if you want to get these notifications indefinitely. That's really another question, and depends on whether we're talking about a jailbreak app or not. But, fundamentally, Darwin notifications don't stop just because you've entered the background. – Nate Jan 17 '13 at 05:07
  • @SpencerWilliams, also take careful note of the last parameter I pass when I register for the notifications. If you aren't using `CFNotificationSuspensionBehaviorDeliverImmediately`, then you very well might not see notifications when your app is in the background. – Nate Jan 17 '13 at 10:23
  • I am using `CFNotificationSuspensionBehaviorDeliverImmediately`, but it seems my app doesn't receive the notifications if it's not in the foreground when the phone is locked, ie if another app is in use and the lock button is pressed, my app doesn't know the device is now locked (without polling periodically) – Spencer Williams Jan 17 '13 at 16:43
  • @SpencerWilliams, hmmm. Works for me. I think you'd probably need to post a new question, and fully list your code, and use case description. Thanks. – Nate Jan 17 '13 at 21:06
  • @Everybody: Please look at this question, because it explains some potential pitfalls with this approach: http://stackoverflow.com/questions/14820249/getting-state-for-system-wide-notifications-in-ios-and-os-x – Victor Ronin Feb 12 '13 at 15:48
  • @nate : Is there any public API to achieve the same? – Sunil Adhyaru Sep 22 '16 at 05:35
  • @SunilAdhyaru, The answer above really **is** using Public APIs. `CFNotificationCenterAddObserver()` is public on iOS. The string name of the event is *undocumented*. But, it's not actually private. You should be able to use this code in an App Store app. I haven't tested it in a while though, so make sure it still woriks. – Nate Sep 22 '16 at 10:57
  • @mcfedr, these APIs are public, so you should be fine with app store approval. The string `com.apple.springboard.hasBlankedScreen` is *undocumented*, which is different than being a *private* API. So, iOS could change and it could stop working in a future release. But, obviously, it's been there for years now. – Nate Oct 28 '16 at 20:24
  • Apple App Store rule changed in 2018 and now rejects apps that use `com.apple.springboard.hasBlankedScreen`. Has anyone found a workaround? – William Grand Aug 09 '18 at 12:24
  • @WilliamGrand this question was explicitly not for the App Store, but your comment is a good one for others. If someone was so inclined, obfuscation might get around that "problem" ;) – Nate Aug 12 '18 at 00:51
  • This is not allowed by apple anymore. https://forums.developer.apple.com/thread/76608 – Abhishek Mar 12 '20 at 15:31
1

You can also subscribe for a notification: "com.apple.springboard.lockstate" and use API SBGetScreenLockStatus to determine status whether device is locked or not.

Victor Ronin
  • 21,310
  • 16
  • 85
  • 171
  • How to call and use it? actually I only want to detect the event when the screen blackens i.e. screen transitions from on/off. Thanks in advance. – Rohit Kashyap Jan 17 '13 at 14:04
  • Info: Its not for a jailbroken device. – Rohit Kashyap Jan 17 '13 at 14:07
  • First notification is used with the same code, which Nate wrote. SBGetScreenLockStatus is described in linked article. These events I added as an answer for "Device gets locked/unlocked". And it works on non jailbroken device. – Victor Ronin Jan 17 '13 at 16:06
  • 1
    @Everybody: Please look at this question, because it explains some potential pitfalls with this approach: http://stackoverflow.com/questions/14820249/getting-state-for-system-wide-notifications-in-ios-and-os-x – Victor Ronin Feb 12 '13 at 15:48