iOS Push Notification Settings - Denied Permission vs Permission Never Requested - ios

Is it possible to differentiate between the cases where
an iOS user has explicitly denied user notification permissions, and
an iOS user has never been prompted for permission?
My situation: In the past, I've prompted for user notification permission, but never kept track of requests myself. Later, I stopped attempting to register any notification settings. Now, I'd like to re-introduce user notifications.
After a significant event in the App, my plan is to display some sort of UI that explains the benefit of opting in to user notifications. However, if the user has already declined, I'd prefer to show a separate UI that can take them into Settings.app.
Currently, I'm using -[UIApplication currentUserNotificationSettings] to grab the current settings, but it appears that this returns UIUserNotificationTypeNone for both of the above described cases.

Personally I haven't found a way to determine this via a quick query of the iOS SDK.
However I have been able to track this myself recording when -[UIApplication application:didRegisterUserNotificationSettings:] is called.
When iOS calls this method, you can be sure the user has been prompted for user notification permissions and has (importantly) either accepted or denied it.
Storing when this occurs you can later check this value to determine if the prompt has been shown before or not.
Example Code:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"ABHasPromptedForUserNotification"];
//... your other notification registration handling...
}
- (BOOL)hasPromptedForUserNotification {
return [[NSUserDefaults standardUserDefaults] boolForKey:#"ABHasPromptedForUserNotification"];
}
FYI: I've found it preferable to set "ABHasPromptedForUserNotification" as true in the in -[UIApplication application:didRegisterUserNotificationSettings:] rather than when I call -[UIApplication registerForRemoteNotifications] as in some situations the user can be shown the prompt multiple times. This can happen if the user backgrounds the app, or takes a call. In these cases the prompt will be hidden by iOS, and shown again if next time you call -[UIApplication registerForRemoteNotifications]. Setting this setting in the delegate avoids thinking the user has been prompted before and won't be prompted again in these edge cases.

No.
And I believe this is done intentionally. Because usual scenario is to register for remote notifications on every app launch. That means that user should not see permissions dialog each time he opens the app. iOS do this automatically. But if you will show extra screen before requesting permissions Apple can't allow you to know if user denied permissions in the past so you can show screen describing how user can enable his permissions through settings each time you want. This will undo all Apple did to stop irritating users.
In your case you must follow same strategy. Show only one type of explanatory screen on both scenarios and save user choice in NSUserDefaults to know if you must not show it again. Users who denied permissions previosly will not see permissions dialog. Though you'll have one benefit for new users (which is obviously you are trying to achieve): you may keep showing explanatory screen many times if user canceled it.

If you support iOS 10 and above the UNUserNotifications framework allows more granularity.
let current = UNUserNotificationCenter.current()
current.getNotificationSettings(completionHandler: { (settings) in
if settings.authorizationStatus == .notDetermined {
// Not requested
}
if settings.authorizationStatus == .denied {
// User said Don't allow
}
})

In case someone need to check the old UIUserNotification API for access permission. code below is tried and tested.
- (BOOL)isUserNotificationAllowed {
UIUserNotificationType types = [[UIApplication sharedApplication] currentUserNotificationSettings].types;
if(types & UIUserNotificationTypeBadge || types & UIUserNotificationTypeSound || types & UIUserNotificationTypeAlert){
return YES;
}
else {
return NO;
}
}

Update for iOS 10.0+.
Apple provide this for checking user's push notification permission status:
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
switch (settings.authorizationStatus) {
case UNAuthorizationStatusNotDetermined:
DDLogDebug(#"Not Determined");
break;
case UNAuthorizationStatusDenied:
DDLogDebug(#"Denied");
break;
case UNAuthorizationStatusAuthorized:
DDLogDebug(#"Authorized");
break;
case UNAuthorizationStatusProvisional:
DDLogDebug(#"Provisional");
break;
}
}];
Check Apple Doc for more information.

Related

Checking for iOS device first unlock to determine if items stored with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly are available

Assume a following situation:
User restarts his/her iPhone.
User lets device locked, does
not unlock it.
Server sends a (silent) push notification on the
device (or anything happens that wakes the application up on the
background, such as Apple Watch extension requests data, etc.).
App wakes up and tries to access Keychain items stored with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly accessibility.
Now, the Keychain items should not be accessible, since device was not unlocked yet. How do I correctly check for this situation?
Note: In my case, the presence of an item stored in the keychain determines if the app is "active" or not, so I would need something to stop this check soon enough, otherwise my app will assume it's not active (value cannot be read) and perform init steps...
I've come across the same situation in my app and here's how I'm checking if the keychain is available (objective-c code):
+ (BOOL)isKeychainAvailable {
NSString *testVal = #"testVal";
NSString *testKey = #"testKey";
[JNKeychain saveValue:testVal forKey:testKey];
NSString *validatedValue = [JNKeychain loadValueForKey:testKey];
[JNKeychain deleteValueForKey:testKey];
return (validatedValue == testVal);
}
I basically save a value in the keychain and try to read it again. If it's not the same as I've just written it means the keychain is unavailable, which also means the phone hasn't been through the first unlock since the keychain should be available after the first unlock thanks to the option kSecAttrAccessibleAfterFirstUnlock.
What I ended up doing in this situation is terminating the app if it was started in the background and the keychain is unavailable:
- (void) methodStartedInBackgroundThatNeedsKeychain {
if (!JNKeychain.isKeychainAvailable && [UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
exit(0);
}
}
ATTENTION! please be aware that Apple strongly discourages the usage of exit(0) when the app is in foreground mode, thats why I'm making sure to only call it in background with [UIApplication sharedApplication].applicationState != UIApplicationStateActive. Here's Apple's QA discussion on the subject: https://developer.apple.com/library/archive/qa/qa1561/_index.html

Blocking user when he doesn't agree to bluetooth or location services

We’ve got an app that requires push notifications, bluetooth and usage of location services acceptance from the user in order to work properly. It’s essential for us, because without Bluetooth enabled user can’t connect to our tracking devices. We know, that we can’t block user on the screen when he doesn’t agree to push notifications, because that’s what App Store Review Guidelines states. The question is: can we block user on the screen until he turns on location and bluetooth?
You need to implement your logic based on your requirements.
However you can check for different conditions for specific services enabled or not.
For Push Notifications:
[[UIApplication sharedApplication] isRegisteredForRemoteNotifications]
For Location services:
if (![CLLocationManager locationServicesEnabled] && [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied)
{
// your code...
}
For Bluetooth:
You can refer this link

Way to find out which button the user selected on the UIAlertView displayed by iOS ,asking permission to allow the app to receive push notification

Is there a way to interact with the alerts show by iOS.For eg: If my app has registered itself for APNS,on first launch, iOS shows an UIAlertView(I am assuming,it is one),giving the user two choices.Is there a way to find out which button the user selected?
I have two alerts that are shown ,during my app launch,one for APNS and the other for Location Services.Is there a way to identify which alert is for what?
In case you can't access those alerts directly I suggest you to look at this problem from another point of view.
For CoreLocation for example you can look at its [CLLocationManager authorizationStatus].
kCLAuthorizationStatusNotDetermined = 0, // User wasn't proposed to use location services
kCLAuthorizationStatusRestricted, // Parental control or something like that
kCLAuthorizationStatusDenied, // User didn't allow this application to use services
kCLAuthorizationStatusAuthorized // User allowed to use his location.
As for APNS there are [[UIApplication sharedApplication] enabledRemoteNotificationTypes]
Source:
https://developer.apple.com/library/ios/documentation/uikit/reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/c_ref/UIRemoteNotificationType
https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html#//apple_ref/doc/c_ref/CLAuthorizationStatus
No. There is no way to get callbacks on the AlertViews created by the OS. Like CoolMonster indicated in his comment, you can find out what the user chose for that particular AlertView and do something based off that.
I don't know about push notifications but here's one (ugly) way of detecting the user's choice when asking for permissions to get user's location:
// After asking for permission, the alert is shown to the user. Since he can't do anything
// at this point but select one of the two options we simply wait...
while(([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined))
{
sleep(1);
}
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized)
{
NSLog(#"User allowed access to gps");
}
else
{
NSLog(#"User denied access to gps");
}
You can get notification types like this:
UIRemoteNotificationType notificationTypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
And may create some hack ways to get user's interaction to Notifications question
Though there may not be a clear-cut way to know what button the user clicked on, you can certainly find out if the user authorized or not the use of push notifications by implementing these UIApplication delegate methods:
application:didRegisterForRemoteNotificationsWithDeviceToken: (User accepted)
application:didFailToRegisterForRemoteNotificationsWithError: (User denied or is not able to accept)

Initial FBSession check consistently fails

I have an app in which the user may choose to login to FB. My code is based largely on the tutorials at FB, and for the most part, the app, and the FB integration works as expected. The problem I am having is that the app is not remembering from launch to launch that the user has selected to connect the app to FB. I put a check into AppDelegate.m to check for a cached FBSession:
if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
// Yes, so just open the session (this won't display any UX).
NSLog(#"The state is IS 'State created token loaded'");
[self openSessionWithAllowLoginUI:NO];
} else {
// No, display the login page.
NSLog(#"The state is NOT 'State created token loaded'");
[self openSessionWithAllowLoginUI:YES];
}
Every time I launch the app, the line "The state is NOT 'State created token loaded'" is displayed in the console. This makes me think I am not doing something right in order to make that FB login persist from launch to launch.
I could really use some advice here. What does one need to do to ensure that "FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded" is true on app launch?
If you have this code right when the app starts, then note that:
FBSession.activeSession
May not be set yet. What you want to do to check for a cached token is something like:
if (![self openSessionWithAllowLoginUI:NO]) {
[self openSessionWithAllowLoginUI:YES];
}
The first call with the "NO" will return synchronously with a value of true if there was a a cached token. It returns no if there is no cached token. At this point you can force the login UX to happen.

Proper way to exit iPhone application?

I am programming an iPhone app, and I need to force it to exit due to certain user actions. After cleaning up memory the app allocated, what's the appropriate method to call to terminate the application?
On the iPhone there is no concept of quitting an app. The only action that should cause an app to quit is touching the Home button on the phone, and that's not something developers have access to.
According to Apple, your app should not terminate on its own. Since the user did not hit the Home button, any return to the Home screen gives the user the impression that your app crashed. This is confusing, non-standard behavior and should be avoided.
Have you tried exit(0)?
Alternatively, [[NSThread mainThread] exit], although I have not tried that it seems like the more appropriate solution.
exit(0) appears to a user as crashes, so show a confirmation message to user. After confirmation suspend(home button press programmatically) and wait 2 seconds while app is going background with animation then exit behind user's view
-(IBAction)doExit
{
//show confirmation message to user
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Confirmation"
message:#"Do you want to exit?"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex != 0) // 0 == the cancel button
{
//home button press programmatically
UIApplication *app = [UIApplication sharedApplication];
[app performSelector:#selector(suspend)];
//wait 2 seconds while app is going background
[NSThread sleepForTimeInterval:2.0];
//exit app when app is in background
exit(0);
}
}
Check the Q&A here: https://developer.apple.com/library/content/qa/qa1561/_index.html
Q: How do I programmatically quit my iOS application?
There is no API provided for gracefully terminating an iOS application.
In iOS, the user presses the Home button to close applications. Should your application have conditions in which it cannot provide its intended function, the recommended approach is to display an alert for the user that indicates the nature of the problem and possible actions the user could take — turning on WiFi, enabling Location Services, etc. Allow the user to terminate the application at their own discretion.
WARNING: Do not call the exit function. Applications calling exit will appear to the user to have crashed, rather than performing a graceful termination and animating back to the Home screen.
Additionally, data may not be saved, because -applicationWillTerminate: and similar UIApplicationDelegate methods will not be invoked if you call exit.
If during development or testing it is necessary to terminate your application, the abort function, or assert macro is recommended
Its not really a way to quit the program, but a way to force people to quit.
UIAlertView *anAlert = [[UIAlertView alloc] initWithTitle:#"Hit Home Button to Exit" message:#"Tell em why they're quiting" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
[anAlert show];
Go to your info.plist and check the key "Application does not run in background". This time when the user clicks the home button, the application exits completely.
Add UIApplicationExitsOnSuspend property on application-info.plist to true.
After some tests, I can say the following:
using the private interface : [UIApplication sharedApplication] will cause the app looking like it crashed, BUT it will call - (void)applicationWillTerminate:(UIApplication *)application before doing so;
using exit(0); will also terminate the application, but it will look "normal" (the springboard's icons appears like expected, with the zoom out effect), BUT it won't call the - (void)applicationWillTerminate:(UIApplication *)application delegate method.
My advice:
Manually call the - (void)applicationWillTerminate:(UIApplication *)application on the delegate.
Call exit(0);.
Your ApplicationDelegate gets notified of intentional quitting by the user:
- (void)applicationWillResignActive:(UIApplication *)application {
When I get this notification I just call
exit(0);
Which does all the work. And the best thing is, it is the useres intent to quit, which is why this should not be a problem calling it there.
On my Audio-App it was necessary to quit the app after people were syncing their device while the music was still playing. As soon as the syncing is complete I get a notification. But quitting the app right after that would actually look like a crash.
So instead I set a flag to REALLY quit the app on the next backgrounding action. Which is okay for refreshing the app after a sync.
My App has been rejected recently bc I've used an undocumented method. Literally:
"Unfortunately it cannot be added to the App Store because it is using a private API. Use of non-public APIs, which as outlined in the iPhone Developer Program License Agreement section 3.3.1 is prohibited:
"3.3.1 Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs."
The non-public API that is included in your application is terminateWithSuccess"
Apple say:
"Warning: Do not call the exit function. Applications calling exit will appear to the user to have crashed, rather than performing a graceful termination and animating back to the Home screen."
I think that this is a bad assumption. If the user tap a quit button and a message appears that say something like: "The application will now quit.", it doesn't appear to be crashed. Apple should provide a valid way to quit an application (not exit(0)).
You should not directly call the function exit(0) as it will quit the application immediately and will look like your app is crashed. So better to show users a confirmation alert and let them do this themselves.
Swift 4.2
func askForQuit(_ completion:#escaping (_ canQuit: Bool) -> Void) {
let alert = UIAlertController(title: "Confirmation!", message: "Do you want to quit the application", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertAction.Style.default, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
completion(true)
}))
alert.addAction(UIAlertAction(title: "No", style: UIAlertAction.Style.cancel, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
completion(false)
}))
self.present(alert, animated: true, completion: nil)
}
/// Will quit the application with animation
func quit() {
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
/// Sleep for a while to let the app goes in background
sleep(2)
exit(0)
}
Usage:
self.askForQuit { (canQuit) in
if canQuit {
self.quit()
}
}
This has gotten a good answer but decided to expand a bit:
You can't get your application accepted to AppStore without reading Apple's iOS Human Interface Guidelines well. (they retain the right to reject you for doing anything against them) The section "Don't Quit Programmatically" http://developer.apple.com/library/ios/#DOCUMENTATION/UserExperience/Conceptual/MobileHIG/UEBestPractices/UEBestPractices.html
is an exact guideline in how you should treat in this case.
If you ever have a problem with Apple platform you can't easily find a solution for, consult HIG. It's possible Apple simply doesn't want you to do it and they usually (I'm not Apple so I can't guarantee always) do say so in their documentation.
Hm, you may 'have to' quit the application if, say, your application requires an internet connection. You could display an alert and then do something like this:
if ([[UIApplication sharedApplication] respondsToSelector:#selector(terminate)]) {
[[UIApplication sharedApplication] performSelector:#selector(terminate)];
} else {
kill(getpid(), SIGINT);
}
We can not quit app using exit(0), abort() functions, as Apple strongly discourage the use of these functions. Though you can use this functions for development or testing purpose.
If during development or testing it is necessary to terminate your
application, the abort function, or assert macro is recommended
Please find this Apple Q&A thread to get more information.
As use of this function create impression like application is crashing. So i got some suggestion like we can display Alert with termination message to aware user about closing the app, due to unavailability of certain functionality.
But iOS Human Interface Guideline for Starting And Stopping App, suggesting that Never use Quit or Close button to terminate Application. Rather then that they are suggesting to display proper message to explain situation.
An iOS app never displays a Close or Quit option. People stop using an
app when they switch to another app, return to the Home screen, or put
their devices in sleep mode.
Never quit an iOS app programmatically. People tend to interpret this
as a crash. If something prevents your app from functioning as
intended, you need to tell users about the situation and explain what
they can do about it.
In addition to the above, good, answer I just wanted to add, think about cleaning up your memory.
After your application exits, the iPhone OS will automatically clean up anything your application left behind, so freeing all memory manually can just increase the amount of time it takes your application to exit.
- (IBAction)logOutButton:(id)sender
{
//show confirmation message to user
CustomAlert* alert = [[CustomAlert alloc] initWithTitle:#"Confirmation" message:#"Do you want to exit?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
alert.style = AlertStyleWhite;
[alert setFontName:#"Helvetica" fontColor:[UIColor blackColor] fontShadowColor:[UIColor clearColor]];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex != 0) // 0 == the cancel button
{
//home button press programmatically
UIApplication *app = [UIApplication sharedApplication];
[app performSelector:#selector(suspend)];
//wait 2 seconds while app is going background
[NSThread sleepForTimeInterval:2.0];
//exit app when app is in background
NSLog(#"exit(0)");
exit(0);
}
}
I used the [[NSMutableArray new] addObject:nil] approach mentioned above to force-quit (crash) the app without making a tell-tale exit(0) function call.
Why? Because my app uses certificate pinning on all network API calls to prevent man-in-the-middle attacks. These include the initialization calls my financial app makes on startup.
If certificate authentication fails, all of my initialization calls error out and leave my app in an indeterminate state. Letting the user go home and then back into the app doesn't help, as unless the app has been purged by the OS it's still uninitialized and untrustworthy.
So, in this one case, we deemed it best to pop an alert informing the user that the app is operating in an insecure environment and then, when they hit "Close", force quit the app using the aforementioned method.
[[UIApplication sharedApplication] terminateWithSuccess];
It worked fine and automatically calls
- (void)applicationWillTerminateUIApplication *)application delegate.
to remove compile time warning add this code
#interface UIApplication(MyExtras)
- (void)terminateWithSuccess;
#end
The user should decide when an app exits.
I don't think it is a good user interaction when an app quits. Therefore there is no nice API for it, only the home button has one.
If there is an error: Implement it better or Notify the user.
If there have to be a restart: Implement it better of Notify the user.
It sounds dumb, but it's bad practice to exit the app without letting the user decide and not notifying him. And since there is a home button for the user interaction, Apple states, there should not be 2 things for the same function (exiting an app).
Exit an app other way than the home button is really non-iOS-esque approach.
I did this helper, though, that use no private stuff:
void crash()
{ [[NSMutableArray new] addObject:NSStringFromClass(nil)]; }
But still not meant for production in my case. It is for testing crash reportings, or to fast restart after a Core Data reset. Just made it safe not to be rejected if function left in the production code.
It may be appropriate to exit an app if it is a long lived app that also executes in the background, for example to get location updates (using the location updates background capability for that).
For example, let's say the user logs out of your location based app, and pushes the app to the background using the home button. In this case your app may keep running, but it could make sense to completely exit it. It would be good for the user (releases memory and other resources that don't need to be used), and good for app stability (i.e. making sure the app is periodically restarted when possible is a safety net against memory leaks and other low memory issues).
This could (though probably shouldn't, see below :-) be achieved with something like:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if (/* logged out */) {
exit(0);
} else {
// normal handling.
}
}
Since the app would then exit out of the background it will not look wrong to the user, and will not resemble a crash, providing the user interface is restored the next time they run the app. In other words, to the user it would not look any different to a system initiated termination of the app when the app is in the background.
Still, it would be preferable to use a more standard approach to let the system know that the app can be terminated. For example in this case, by making sure the GPS is not in use by stopping requesting location updates, including turning off show current location on a map view if present. That way the system will take care of terminating the app a few minutes (i.e. [[UIApplication sharedApplication] backgroundTimeRemaining]) after the app enters the background. This would get all the same benefits without having to use code to terminate the app.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if (/* logged out */) {
// stop requesting location updates if not already done so
// tidy up as app will soon be terminated (run a background task using beginBackgroundTaskWithExpirationHandler if needed).
} else {
// normal handling.
}
}
And of course, using exit(0) would never be appropriate for the average production app that runs in the foreground, as per other answers that reference http://developer.apple.com/iphone/library/qa/qa2008/qa1561.html
Swift 4.2 (or older)
Library called Darvin can be used.
import Darwin
exit(0) // Here you go
NB: This is not recomanded in iOS applications.
Doing this will get you crash log.
In iPadOS 13 you can now close all scene sessions like this:
for session in UIApplication.shared.openSessions {
UIApplication.shared.requestSceneSessionDestruction(session, options: nil, errorHandler: nil)
}
This will call applicationWillTerminate(_ application: UIApplication) on your app delegate and terminate the app in the end.
But beware of two things:
This is certainly not meant to be used for closing all scenes. (see https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/multiple-windows/)
It compiles and runs fine on iOS 13 on an iPhone, but seems to do nothing.
More info about scenes in iOS/iPadOS 13: https://developer.apple.com/documentation/uikit/app_and_environment/scenes

Resources