When an application need to register for push notification (UIApplication registerForRemoteNotificationTypes) a popup show Allow/Don't choice.
Is there a way to track when the user take this choice ?
Because the solution:
NSUInteger rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
is fine, but until the user touch something it's NO by default. I should only check this config after the user make a choice.
The consequence is that in my EasyAPNS server most of the application are in 'disabled' mode until the user relaunch them (because the second time the correct config will be pushed to my sever). So with the first launch the real choice of the user is probably not taken into account (if you accept really rapidly, before my app register to EasyAPNS then your choice is reflected back on the server at first launch)
Any idea ?
There seems to be no way to determine whether the allow pop-up has been shown. I rely on user defaults to keep track of this:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
BOOL didRegisterForPush = [[NSUserDefaults standardUserDefaults] boolForKey:#"didRegisterForPush"];
if (!didRegisterForPush) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"didRegisterForPush"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
// .. send deviceToken to server
}
Now you can determine the authorization state using:
- (PushAuthorizationStatus)pushAuthorizationStatus
{
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types) {
return kPushAuthorizationStatusAuthorized;
}
BOOL didRegisterForPush = [[NSUserDefaults standardUserDefaults] boolForKey:#"didRegisterForPush"];
if (didRegisterForPush) {
return kPushAuthorizationStatusDenied;
}
return kPushAuthorizationStatusNotDetermined;
}
Using this you can send the NotDetermined state to the server instead of Denied.
Under iOS 8 and later the procedure is a little different. In iOS 8 the enabledRemoteNotificationTypes method was replaced by isRegisteredForRemoteNotifications.
However isRegisteredForRemoteNotifications always returns YES if the app attempted to register for notifications, regardless of whether the user actually allowed them or not.
To determine whether the user actually allowed notifications, use the function provided by #Lefteris here:
- (BOOL)pushNotificationsEnabled {
if ([[UIApplication sharedApplication] respondsToSelector:#selector(currentUserNotificationSettings)]) {
UIUserNotificationType types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];
return (types & UIUserNotificationTypeAlert);
}
else {
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
return (types & UIRemoteNotificationTypeAlert);
}
}
Related
I am working on a chat app in react-native iOS. I want to do a badge increment on the main app icon when a notification is received, when the app is killed or force quit by the user. It works well when the app is in the background mode. But the didReceiveRemoteNotification method is not called when the app is inactive. Any idea on this? I added code inside the didReceiveRemoteNotification method of AppDelegate.
Added the following code set in AppDelegate file
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
[RNNotifications didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"APPDELEGATE: didReceiveRemoteNotification:fetchCompletionHandler %#", userInfo);
int badge = (int) application.applicationIconBadgeNumber;
if ( application.applicationState == UIApplicationStateInactive ) {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge+1];
}
else if(application.applicationState == UIApplicationStateBackground) {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge+1];
}
else if(application.applicationState == UIApplicationStateActive) {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge+1];
}
completionHandler(UIBackgroundFetchResultNewData);
}
You need to create a UNNotificationServiceExtension to handle notifications when app is in background/killed.
It's a small application that will be executed on notification reception and has limited time to edit its content before presenting it to the user.
When you receive a notification in it's didReceive(_:withContentHandler:) delegate callback you can modify badge value of UNNotificationContent
In order to catch the incoming notification when the app is in the background or killed, use a UNNotificationServiceExtension in your project. The Info.plist for the UNNotificationServiceExtension (not the normal Info.plist for the main app; that one has normal things for the main app) might look something like
In the - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler of the UNNotificationServiceExtension, you can update the badge the following way:
self.bestAttemptContent = [request.content mutableCopy]; to get the request's content
self.bestAttemptContent.badge = <desired_integer_value> , where <desired_integer_value> would be the integer that you wish to put for the badge count.
self.contentHandler(self.bestAttemptContent); to complete the update of the content.
In many cases, the badge count may need to reflect a value (like number of unread chat messages) for a particular user. For that, you can use a shared user defaults. In fact NSUserDefaults supports the concept of app suite to allow such sharing. See Apple documentation for more details. In particular,
You can use this method when developing an app suite, to share preferences or other data among the apps, or when developing an app extension, to share preferences or other data between the extension and its containing app.
The argument and registration domains are shared between all instances of NSUserDefaults.
In a Constants.h file, have something to track individual counts for each user like
#define NOTIFICATIONS_UNREAD_SHARED [NSString stringWithFormat:#"notificationsUnread-%#",[mySharedDefaults objectForKey:USERNAME]]
and in your app, you would save the individual counts for each user, to the app suite's shared user defaults, with something like
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.yourCompany.yourAppname"];
if ([[NSUserDefaults standardUserDefaults] objectForKey:USERNAME]) {
mySharedDefaults setObject:[[NSUserDefaults standardUserDefaults] objectForKey:USERNAME] forKey:USERNAME];
[mySharedDefaults setObject:[NSNumber numberWithInteger:arrExistingRead.count] forKey:NOTIFICATIONS_READ_SHARED];
[mySharedDefaults setObject:[NSNumber numberWithInteger:([AppConstant sharedconstant].countObj.arrAllMessages.count - arrExistingRead.count)] forKey:NOTIFICATIONS_UNREAD_SHARED];
[mySharedDefaults synchronize];
}
Then in your UNNotificationServiceExtension, you would do something like
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.yourCompany.yourAppname"];
if ([mySharedDefaults objectForKey:NOTIFICATIONS_UNREAD_SHARED]) {
if ([mySharedDefaults objectForKey:USERNAME]) {
self.bestAttemptContent.badge = [NSNumber numberWithInt:[[mySharedDefaults objectForKey:NOTIFICATIONS_UNREAD_SHARED] intValue]+1];
} else { // username somehow not set; reset badge to 0
self.bestAttemptContent.badge = #0;
}
} else { // notifications unread count not found for this user; reset badge to 0
self.bestAttemptContent.badge = #0;
}
Troubleshooting
In case the extension doesn't appear to be receiving the push notifications, some things to verify:
Look at the build targets. Besides the main app, there should be one for the extension too.
In the settings for the main app, it should associate with the UNNotificationServiceExtension :
You need to set the badge field in the payload in the push notification.
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification
You will have to do the calculation server side.
Is the a way of knowing if the user declined the push notifications permissions?
I know that didRegisterForRemoteNotificationsWithDeviceToken will be called in case the user allowed push notifications - but what will be called if he didn't?
A simple method to check whether notification is enabled in app or not.
-(BOOL)checkNotificationEnabled
{
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone)
{
return FALSE; //Notification not enabled
}
else
{
return TRUE;//Notification is enabled
}
}
else // for iOS 8 devices checking will be different
{
return [[UIApplication sharedApplication] isRegisteredForRemoteNotifications];
// if return TRUE then Notification is enabled
// if return False then Notification is not enabled
}
}
According to the UIApplicationDelegate Protocol Reference, application(_:didFailToRegisterForRemoteNotificationsWithError:) is called if an error occurs during the registration process.
After you call the registerForRemoteNotifications method of the UIApplication object, the app calls this method when there is an error in the registration process.
For more information about how to implement remote notifications in your app, see Local and Remote Notification Programming Guide.
You can implement in following way. Suppose that if user allows notification then you can store device token into user default. Now, next time you can check with same user default weather user allowed or not.
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
NSString *aStrToken = [[deviceToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
aStrToken = [aStrToken stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString *aStrStoredToken = [_appDelegate validateString:[[NSUserDefaults standardUserDefaults]objectForKey:#"deviceID"] :#""];
if([aStrStoredToken length]==0) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"getDeviceToken" object:nil];
[self setApplicationBadgeNumber:0];
[[NSUserDefaults standardUserDefaults]setObject:aStrToken forKey:#"deviceID"];
[[NSUserDefaults standardUserDefaults]synchronize];
if(![[NSUserDefaults standardUserDefaults]objectForKey:#"setDeviceID"]) {
[[NSUserDefaults standardUserDefaults]setObject:#"0" forKey:#"setDeviceID"];
[[NSUserDefaults standardUserDefaults]synchronize];
} else {
[[NSUserDefaults standardUserDefaults]setObject:#"1" forKey:#"setDeviceID"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
}
}
I want to implement a custom screen that informs my users why I'm about to ask for push notification permissions. After they press a button in that custom screen I present the iOS push notification permission dialog with [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
I only want to show this custom screen once if the user hasn't already seen the push notification permission dialog. I cannot use [[UIApplication sharedApplication] enabledRemoteNotificationTypes] == UIRemoteNotificationTypeNone as this will also return 'none' if the user decided to not allow push notifications.
Any ideas anyone?
You could use NSUserDefaults :
#define kPushNotificationRequestAlreadySeen #"PushNotificationRequestAlreadySeen"
if(![[NSUserDefaults standardUserDefaults] boolForKey:kPushNotificationRequestAlreadySeen]) {
// Notify the user why you want to have push notifications
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:... delegate:self ...];
[alertView show];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kPushNotificationRequestAlreadySeen];
}
else {
// Already allowed -> register without notifying the user
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
}
And
- (void)alertView:(UIAlertView*)alertView didDismissWithButtonIndex:(NSInteger)index {
// Actually registering to push notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
}
This is just a work-around but will work in the majority of cases.
When iOS prompts the user to allow push notifications, applicationWillResignActive is called.
After calling registerForRemoteNotificationTypes...start a timer (or dispatch_after) that will show a new alert to the user explaining that they'll need to use the Settings app to enable notifications. Then in applicationWillResignActive cancel the timer. This way, the explanation alert will only show if applicationWillResignActive is never called.
The only problem I see with this is if the app resigns active for other reasons at the exact time that the timer is going...but this puts you in no worse a situation that you're already in and has the advantage of working in the majority of cases.
The solution that I've found is a bit of a hack, but it works.
You need to call registerUserNotificationSettings for two different notificationSettings - one without a notificationCategory and one with a notificationCategory:
//Request notification permission
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
//Request notification permission again, but with a category with no actions
UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory alloc] init];
category.identifier = #"com.xyz.markNotificationPopupShownCategoryIdentifier";
UIUserNotificationSettings *notificationSettingsWithCategory = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:[NSSet setWithObject:category]];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettingsWithCategory];
The didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings method in the app delegate will be called two times, and irrespective of the user's answer in the permission notification, after the second call, the current notification settings will contain the category. As long as the category count is greater than 0, you can know for sure that the notifications permission dialog has been shown:
if ([UIApplication sharedApplication].currentUserNotificationSettings.categories.count > 0) {
NSLog(#"Notifications permission has been asked");
} else {
NSLog(#"Notifications permission hasn't been asked");
}
I want to fire some event if user taps on "Don't Allow" button on the apple's push notification alert message. Is there any notification getting fired or any other way to detect this action from the user?
I'm sure someone will need a solid and simple answer to this (like I once did) -- so here you go. Directly after calling [[UIApplication sharedApplication] registerForRemoteNotifications]; you can use NSNotificationCenter beautifully like so:
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
if ([[UIApplication sharedApplication] isRegisteredForRemoteNotifications]) {
//user tapped "Allow"
}
else{
//user tapped "Don't Allow"
}
}];
NOTE: My device is currently running iOS 9.2, my Xcode is version 7.2, and my Deployment Target is 8.0.
I do't thing so that we can detect what UIAlertView button user pressed as there is no any kind of callback methods or delegate etc provided in iOS.
Only if you pressed Don't Allow this will disable the push notification service for that particular iOS App and if YES then enable.
And After that through the code we can check and ensure about it using.
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone)
// NONE
iOS8 comes with rregisterUserNotificationSettings: delegate method. Using this method we can do some patches.Please review them and Let us know your comments.Please these is only work with iOS8.
=> Add/Register Notification.
if ([[UIApplication sharedApplication] respondsToSelector:#selector(registerUserNotificationSettings:)])
{
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
}
=> Here Below method is called After Alert Notification Fire. Using Any of the Button Action(Don't allow or Allow) we force fully register Notification. and Dow with some patch Here.
#ifdef __IPHONE_8_0
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
//register to receive notifications
[application registerForRemoteNotifications];
}
=>We do some trick here
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
NSLog(#"devToken: %#",devToken);
#if !TARGET_IPHONE_SIMULATOR
NSString* deviceToken = [[[[[devToken description]
stringByReplacingOccurrencesOfString: #"<" withString: #""]
stringByReplacingOccurrencesOfString: #">" withString: #""]
stringByReplacingOccurrencesOfString: #" " withString: #""] retain];
NSLog(#"deviceToken : %#",deviceToken);
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if((![[standardDefaults valueForKey:#"DeviceToken"] isEqualToString:deviceToken]) || [standardDefaults valueForKey:#"DeviceToken"]==nil){
[self sendProviderDeviceToken:deviceToken];
}else{
//Do Some Stuff Here
}
}
My app registers the account at login in my server to enable push notification for a chat. Yet I haven't implemented yet the unregistration of the account at logout, so in this moment if I do the login with 2 accounts in the same device it can take the notification of both the accounts. At the same time, my notification center has a POST service which unregisters the 'login_name+ device token' from receive notification center. Where should I call it? Do I have to use unregisterForRemoteNotifications? I just want to unregister the account+Device token from push notification, not to disable the entire app notification forever.
Can I save my device token on didRegisterForRemoteNotificationsWithDeviceToken function like
$ [[NSUserDefaults standardUserDefaults] setObject:hexToken forKey:DEVICE_KEY];
and then, at logout, call my POST function "removeDeviceToken" like
NSString *deviceToken = [userDefaults objectForKey:DEVICE_KEY];
if(deviceToken != NULL){
[self.engine removeDeviceToken:deviceToken];
}
You can easily enable and disable push notifications in your application by calling
To register, call: registerForRemoteNotificationTypes:
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
To unregister, call: unregisterForRemoteNotificationTypes:
[[UIApplication sharedApplication] unregisterForRemoteNotifications];
For check use
Enable or Disable iPhone Push Notifications try this code
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone)
// Yes it is..
I'm not sure if i got it correctly, but if you don't want to disable push notifications for the app, then you should't call the unregisterForRemoteNotifications. What you can do is, when the user taps the logout button, you can make a logout request to your server, which then removes the notificationID from that account, and after the logout request is completed, you just perform the logout locally (update UI etc).
More info about comment:
Yes, first of all, you should call registerForRemoteNotificationTypes method at every launch, because device token can change. After the delegate method
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
is called, you can get the device token and save it to NSUserDefault. That way when the user logs in, you can get the up-to-date device token (if changed), and send it to your server to be added to that account.
So the code might look like this
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
NSString *newToken = [devToken description];
newToken = [newToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
newToken = [newToken stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString *notificationID = [newToken description];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:notificationID forKey:#"notification_id"];
[prefs synchronize];
}
So, now when the user logs in, just get the notification ID and send it to your server
- (IBAction)userTappedLoginButton {
// Make your login request
// You can add the notification id as a parameter
// depending on your web service, or maybe make
// another request just to update notificationID
// for a member
NSString *notificationID = [[NSUserDefaults standardUserDefaults] objectForKey:#"notification_id"];
...
...
}
With Swift:
To Register,
UIApplication.shared.registerForRemoteNotifications()
To unregister,
UIApplication.shared.unregisterForRemoteNotifications()
In general it is a bad idea to unregisterForRemoteNotifications after logout and reregister after login. The reason is simple: if the user logins with another account and you don't specifically check for token overlapping in server, the user will start receiving double notifications.