Cannot display UIUserNotificationActions in foreground (default context) - ios

I am trying to get a set of user notification actions to display with more than 2 buttons. The notification itself utilises the minimal context when app is in the background and this works fine.
When the notification is called while in the foreground, the application:didReceiveLocalNotification: is called as per the documentation on the UILocalNotification class.
How is an alert with more than two buttons (provided by the default context of the action category) ever displayed?
The code below follows the documentation for using more than 2 buttons but only calls application:didReceiveLocalNotification: when the app is in the foreground:
// Selecting YES activates the app in the foreground
UIMutableUserNotificationAction *actionYes = [ UIMutableUserNotificationAction new ];
actionYes.identifier = #"actionYes";
actionYes.title = #"Yes";
actionYes.activationMode = UIUserNotificationActivationModeForeground;
actionYes.destructive = NO;
actionYes.authenticationRequired = NO;
// Selecting NO activates the app in the background
UIMutableUserNotificationAction *actionNo = [ UIMutableUserNotificationAction new ];
actionNo.identifier = #"actionNo";
actionNo.title = #"No";
actionNo.activationMode = UIUserNotificationActivationModeBackground;
actionNo.destructive = NO;
actionNo.authenticationRequired = NO;
// Selecting NEVER activates the app in the background
UIMutableUserNotificationAction *actionNever = [ UIMutableUserNotificationAction new ];
actionNever.identifier = #"actionNever";
actionNever.title = #"Never";
actionNever.activationMode = UIUserNotificationActivationModeBackground;
actionNever.destructive = YES;
actionNever.authenticationRequired = NO;
UIMutableUserNotificationCategory *actionCategory = [ UIMutableUserNotificationCategory new ];
actionCategory.identifier = #"actionCategory";
[ actionCategory setActions: #[ actionYes, actionNo, actionNever ] forContext: UIUserNotificationActionContextDefault ];
[ actionCategory setActions: #[ actionNo, actionYes ] forContext: UIUserNotificationActionContextMinimal ];
NSSet *categories = [ NSSet setWithObjects: actionCategory, nil] ;
UIUserNotificationType notificationType = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *notificationSettings = [ UIUserNotificationSettings settingsForTypes: notificationType categories: categories ];
// The categories have to be registered for notification settings to the app
[ [ UIApplication sharedApplication ] registerUserNotificationSettings: notificationSettings ];
UILocalNotification *localNotification = [ UILocalNotification new ];
localNotification.fireDate = [ NSDate dateWithTimeIntervalSinceNow: 5.0 ];
localNotification.alertBody = #"Testing user notifications message";
localNotification.category = #"actionCategory"; // Same as category identifier
[ [ UIApplication sharedApplication ] scheduleLocalNotification: localNotification ];

For more than 2 buttons, the user has to select "Alerts" as opposed to "Banners" in the app settings menu. The alert will have an Options button that displays the buttons in a list between Open and Close.
The Alert/Banner options cannot be set programmatically.
If an app is in the foreground all notifications will get sent directly to the app delegate application:didReceiveLocalNotification: without exception; this entails that actions cannot be obtained while the app is in the foreground.
I hope this helps anyone else reading through the Apple documentation.

Related

Clear already displayed local notification programmatically

I need to clear already displayed local notification from Notification Centre programmatically. Following this link I implemented suggested solution, but the problem is that eventArray from this example always has 0 elements. When I swipe down to display Notification Centre I see 4 notifications that I've created previously. So in this case I expect this array to have 4 elements, but it has 0. Any idea why is this so? I've tried on iOS 8.3 and 9.2.1 and array is 0 on both of them.
iOS has 2 ways of presenting local notifications:
From Notification Center:
You can't swipe away notification from left to right.
You can swipe notification from right to left (deleting single notification from the list).
You can click on the notification, after which your app will start and notification will be removed from Notification Center (handled by iOS system)
From Lock screen:
Available only if you enable this setting from iPhone/iPad settings: http://www.imore.com/how-disable-notification-center-lock-screen-your-iphone-and-ipad
You can swipe notification from left to right (your app will be started, handled by iOS). In this case notification from Notification Center is not deleted (iOS doesn't delete it, and doesn't allow deleting a single notification from code after notification is already presented to the user in Notification Center).
You can swipe notification from right to left (deleting single notification from the list, handled by iOS. Notification Center notification is also deleted, handled by iOS).
You can't click on the notification.
EDIT:
Here is code sample how I did it:
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = #"1";
localNotification.alertTitle = #"1";
localNotification.userInfo = uniqueDictIdentifier1;
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
UILocalNotification *localNotification2 = [[UILocalNotification alloc] init];
localNotification2.alertBody = #"2";
localNotification2.alertTitle = #"2";
localNotification2.userInfo = uniqueDictIdentifier2;
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification2];
....
//2 more notifications are created like this
And then there is code for filtering all notifications:
NSArray *eventArray = [[UIApplication sharedApplication] scheduledLocalNotifications];
for (int i=0; i<[eventArray count]; i++) {
UILocalNotification* oneEvent = [eventArray objectAtIndex:i];
NSDictionary *userInfoCurrent = oneEvent.userInfo;
if ([userInfoCurrent isEqualToDictionary:uniqueDictIdentifier1]) {
[[UIApplication sharedApplication] cancelLocalNotification:oneEvent];
break;
}
}
For saving a notification for a unique id
NSDictionary * infoDict = #{ #"alarmUiqueId" : uID,
};
NSLog(#"%#",infoDict);
NSDateComponents *comp = [[NSCalendar currentCalendar] components:NSCalendarUnitSecond
fromDate:fireDate];
fireDate = [fireDate dateByAddingTimeInterval:-comp.second];
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
localNotif.fireDate = fireDate;
localNotif.timeZone = [NSTimeZone localTimeZone];
localNotif.alertBody = desString;
localNotif.userInfo = infoDict;
localNotif.repeatInterval = NSCalendarUnitDay;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif]
and for delete a paritcular notification write this code.
NSArray *notificationArray = [[UIApplication sharedApplication] scheduledLocalNotifications];
for(UILocalNotification *notification in notificationArray)
{
NSLog(#"%#",[notification.userInfo valueForKey:#"alarmUiqueId"]);
if ([[notification.userInfo valueForKey:#"alarmUiqueId"] isEqualToNumber: health.uniqueId])
{
[[UIApplication sharedApplication] cancelLocalNotification:notification] ;
}
}
My understanding is below:
You can not get the information of already notified LocalNotification with scheduledNotifications.
My resolution is that just keep UILocalNotification instance in your singleton object in your application, and call cancelLocalNotification with the instance when you want to delete from Notification Center.
Is this could be your help ?

Execute an NSTimer in background

I need to run an NSTimer in an app even after the user minimizes the app. The Timer is used to hit a web service every 15 seconds and after 4 minutes I need to perform certain navigations. How can this be done?
I know this question has been asked earlier too but I have not been able to find an appropriate answer.
You need a reason for it to be possible.
If it's for navigation app, VOIP, or just for background downloads (in some cases), it's possible.
But otherwise, you can't do it.
Before you proceed, think about why it isn't easy to make a timer run when the app is in the background. It's because it empties the phone's battery. You get unhappy customers if you do that. Which Apple doesn't care much about, but Apple gets unhappy customers, which they care about a lot. Under Settings -> Battery Apple will tell the user which app used how much energy in the last 24 hours / 6 days, so people can find your app if it does waste energy in the background and throw it out.
You can configure you app to run in the background, but you have no control over how frequently iOS calls you app in the background.
I know this is a really bad solution and misuse of the service, but you could initiate a background file download and make your server to send data so slowly that it takes about 4 minutes. iOS will report to your app when the transfer is complete. If your app is not terminated iOS also reports the download progress to your app. But you can't open the app ui when the transfer is complete if the app is not in the foreground.
Create the configuration object using the
backgroundSessionConfigurationWithIdentifier: method of
NSURLSessionConfiguration.
Set the value of the configuration object’s
sessionSendsLaunchEvents property to YES.
if your app starts transfers
while it is in the foreground, it is recommend that you also set the
discretionary property of the configuration object to YES.
Configure
any other properties of the configuration object as appropriate.
Use
the configuration object to create your NSURLSession object.
When all of the tasks associated with a background session are
complete, the system relaunches a terminated app (assuming that the
sessionSendsLaunchEvents property was set to YES and that the user did
not force quit the app) and calls the app delegate’s
application:handleEventsForBackgroundURLSession:completionHandler:
method.
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
AppDelegate.h // using local notification to get some function call
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self getNotificationPermission];
return YES;
}
This method used for permission
-(void)getNotificationPermission{
// Accept Action
UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc] init];
acceptAction.identifier = #"Accept";
acceptAction.title = #"Yes i am OK";
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
acceptAction.destructive = NO;
acceptAction.authenticationRequired = NO;
// Reject Action
UIMutableUserNotificationAction *rejectAction = [[UIMutableUserNotificationAction alloc] init];
rejectAction.identifier = #"Reject";
rejectAction.title = #"On i am Not OK";
rejectAction.activationMode = UIUserNotificationActivationModeBackground;
rejectAction.destructive = YES;
rejectAction.authenticationRequired = YES;
// Reply Action
UIMutableUserNotificationAction *replyAction = [[UIMutableUserNotificationAction alloc] init];
replyAction.identifier = #"Reply";
replyAction.title = #"Reply";
replyAction.activationMode = UIUserNotificationActivationModeForeground;
replyAction.destructive = NO;
replyAction.authenticationRequired = YES;
// Email Category
UIMutableUserNotificationCategory *emailCategory = [[UIMutableUserNotificationCategory alloc] init];
emailCategory.identifier = #"Email";
[emailCategory setActions:#[acceptAction,rejectAction,replyAction] forContext:UIUserNotificationActionContextDefault];
[emailCategory setActions:#[acceptAction,rejectAction] forContext:UIUserNotificationActionContextMinimal];
// Download Action
UIMutableUserNotificationAction *downloadAction = [[UIMutableUserNotificationAction alloc] init];
downloadAction.identifier = #"Download";
downloadAction.title = #"Download";
downloadAction.activationMode = UIUserNotificationActivationModeForeground;
downloadAction.destructive = NO;
downloadAction.authenticationRequired = YES;
UIMutableUserNotificationAction *cancelAction = [[UIMutableUserNotificationAction alloc] init];
cancelAction.identifier = #"Cancel";
cancelAction.title = #"Cancel";
cancelAction.activationMode = UIUserNotificationActivationModeForeground;
cancelAction.destructive = YES;
cancelAction.authenticationRequired = YES;
// Image Category
UIMutableUserNotificationCategory *imageCategory = [[UIMutableUserNotificationCategory alloc] init];
imageCategory.identifier = #"Image";
[imageCategory setActions:#[downloadAction,cancelAction] forContext:UIUserNotificationActionContextDefault];
[imageCategory setActions:#[downloadAction,cancelAction] forContext:UIUserNotificationActionContextMinimal];
NSSet *categories = [NSSet setWithObjects:emailCategory,imageCategory, nil];
UIUserNotificationType notificarionType = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
// Register notification types and categories
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:notificarionType categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
// This line of code is required for remote push notification
//[[UIApplication sharedApplication] registerForRemoteNotifications];
}
-(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
//UIUserNotificationType notificationtype = [notificationSettings types];
}
// You can check the current user notifiartions settings whenever you need it
-(void)getReadyForNotification{
//UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings];
//[self checkSettings:settings];
}
// When appliation is in foregrounf this methid will get caled if you have scheduled a local notification
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
//CLRegion *region = notification.region;
if ([notification.category isEqualToString:#"Image"]) {
NSLog(#"Image category notification is presented to the user");
}else if([notification.category isEqualToString:#"Email"]){
NSLog(#"Email category notification is presented to the user");
}
}
-(void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler{
if ([identifier isEqualToString:#"Accept"]) {
[self printAlert:#"Accept"];
}else if ([identifier isEqualToString:#"Reject"]) {
[self printAlert:#"Reject"];
}else if ([identifier isEqualToString:#"Reply"]) {
[self printAlert:#"Reply"];
}else if ([identifier isEqualToString:#"Download"]) {
[self printAlert:#"Download"];
}else if ([identifier isEqualToString:#"Cancel"]) {
[self printAlert:#"Cancel"];
}
completionHandler();
}
-(void)printAlert:(NSString *)title
{
NSLog(#"%# button is selected",title);
if ([title isEqualToString:#"Accept"])
{
}
else if ([title isEqualToString:#"Reject"])
{
}
}
// Callback for remote notification
-(void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler{
NSLog(#"handleActionWithIdentifier forRemoteNotification");
}
Set Local Notification on ViewController
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:10];
localNotification.alertBody = #"How you are Feeling Today ?";
localNotification.category = #"Email"; // This should match categories identifier which we have defined in App delegate
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
Using this code you will Get local notification after every cycle of time period if user select notification in foreground then open app in particular stage or screen you want. this code is also not breaching apple guidelines.

About local notifications on Apple watch app

I have managed scheduling local notification on ios app and have tested them on static notifications interface on apple watch. But, there is a glitch (maybe not?) with that, static local notification interface is being displayed even when the watch app is still running i.e. on the foreground (iphone app is backgrounded and iphone is locked). Also when watch app is active, iphone app is backgrounded, and iphone is NOT locked, iphone is displaying the notification. This is really weird to me.
I'm scheduling local notification as below:
NSString *textToShow = nil;
NSSet *categories = nil;
textToShow = text;
UIMutableUserNotificationAction *notificationAction1 = [[UIMutableUserNotificationAction alloc] init];
notificationAction1.identifier = #"openWKApp";
notificationAction1.title = localStr(#"View");
notificationAction1.activationMode = UIUserNotificationActivationModeForeground;
notificationAction1.destructive = NO;
notificationAction1.authenticationRequired = NO;
UIMutableUserNotificationCategory *notificationCategory = [[UIMutableUserNotificationCategory alloc] init];
notificationCategory.identifier = #"openWKApp";
[notificationCategory setActions:#[notificationAction1] forContext:UIUserNotificationActionContextDefault];
[notificationCategory setActions:#[notificationAction1] forContext:UIUserNotificationActionContextMinimal];
categories = [NSSet setWithObjects:notificationCategory, nil];
UIUserNotificationType notificationType = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:notificationType categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = date;
localNotification.alertBody = #"Notification body";
localNotification.applicationIconBadgeNumber = 1;
localNotification.userInfo = info;
localNotification.category = #"openWKApp";
localNotification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
If anybody has faced the same and fixed or found the way around, please, help!
Ended up canceling notifications on willActivate, and re-scheduling on didDeactivate of watchkit. Still, it's really weird to receive notification when watchkit app is running.

Why does UIAlert generated by UILocalNotification display "Options" instead of action title?

I am trying to set up an Apple Watch app (presenting flashcards) which fires a notification when a new question is due. If the iPhone is open but the app is in the background it fires a UIAlert, and clicking the action button ("View") opens the app. If the iPhone is locked the notification fires on the Apple Watch with a short glance-long glance with a button "View Question" which opens the Apple Watch app.
It works, but the UIAlert on the iPhone has an annoying "Options" button which requires you to click "Options" to open another UIAlert with two action buttons ("View" and "View Question"), either of which will open the iPhone app. Here is the relevant code which first cancels all previous notifications (as the app allows you to answer a new question before the timer is finished, at which point the timer is reset). It then sets up a notification to be displayed on the iPhone; and finally sets up a notification for the Apple Watch.
// Cancel previous notifications
[[UIApplication sharedApplication] cancelAllLocalNotifications];
// Set up iPhone notification
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:(int)dateLatency];
localNotification.timeZone = [NSTimeZone defaultTimeZone];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSInteger intVal = [prefs integerForKey:#"timeLag"];
localNotification.alertBody = [NSString stringWithFormat:#"%ld-%d", (long)intVal, (int)dateLatency];
localNotification.alertAction = [NSString stringWithFormat:#"View"];
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.alertLaunchImage = nil;
localNotification.category = #"qDue";
localNotification.userInfo = [self createAPNS:#"999"]; // function that constructs userInfo json structure
// Set up apple watch notification
NSMutableSet *categories = [[NSMutableSet alloc] init];
UIMutableUserNotificationAction *viewQuestion = [[UIMutableUserNotificationAction alloc] init];
viewQuestion.title = #"View Question";
viewQuestion.identifier = #"viewQuestion";
viewQuestion.activationMode = UIUserNotificationActivationModeForeground;
viewQuestion.authenticationRequired = false;
UIMutableUserNotificationCategory *questionCategory = [[UIMutableUserNotificationCategory alloc] init];
questionCategory.identifier = #"qDue";
[questionCategory setActions:#[viewQuestion] forContext:UIUserNotificationActionContextDefault];
[categories addObject:questionCategory];
UIUserNotificationType notificationType = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:notificationType categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
// Schedule it with the app
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
I can get rid of the "Options" button and the extra UIAlert by deleting the apple watch notification, but then there is no notification on the apple watch.
Can anybody tell me why the "Options" button appears, and how to get rid of it?

"WatchKit Simulator Actions" not working on actual apple watch device

I've tested the simulated apple watch push notification and it works fine...
However when I tried to send it to the actual apple watch, the button doesn't appear... why is it so?
And it doesn't vibrate or beep when I send the push notification in json format instead of text...
{
"aps": {
"alert": {
"body": "Great!\nNew input",
"title": "Optional title"
},
"category": "sCategory"
},
"WatchKit Simulator Actions": [
{
"title": "Details",
"identifier": "sDetailsButtonAction"
}
],
"customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App."
}
Have a look at this.
To specify custom action buttons, you need to create a custom notification category. When you create the notification, set the category to your customized one.
The example from the Apple Documentation:
func registerSettingsAndCategories() {
var categories = NSMutableSet()
var acceptAction = UIMutableUserNotificationAction()
acceptAction.title = NSLocalizedString("Accept", comment: "Accept invitation")
acceptAction.identifier = "accept"
acceptAction.activationMode = UIUserNotificationActivationMode.Background
acceptAction.authenticationRequired = false
var declineAction = UIMutableUserNotificationAction()
declineAction.title = NSLocalizedString("Decline", comment: "Decline invitation")
declineAction.identifier = "decline"
declineAction.activationMode = UIUserNotificationActivationMode.Background
declineAction.authenticationRequired = false
var inviteCategory = UIMutableUserNotificationCategory()
inviteCategory.setActions([acceptAction, declineAction], forContext: UIUserNotificationActionContext.Default)
inviteCategory.identifier = "invitation"
categories.addObject(inviteCategory)
// Configure other actions and categories and add them to the set...
var settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: categories)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
}
From a previous awnser : How to handle action buttons in push notifications
1) Register for the category in your appDelegate's parent app :
- (void)registerSettingsAndCategories {
// Create a mutable set to store the category definitions.
NSMutableSet* categories = [NSMutableSet set];
// Define the actions for a meeting invite notification.
UIMutableUserNotificationAction* acceptAction = [[UIMutableUserNotificationAction alloc] init];
acceptAction.title = NSLocalizedString(#"Repondre", #"Repondre commentaire");
acceptAction.identifier = #"respond";
acceptAction.activationMode = UIUserNotificationActivationModeForeground; //UIUserNotificationActivationModeBackground if no need in foreground.
acceptAction.authenticationRequired = NO;
// Create the category object and add it to the set.
UIMutableUserNotificationCategory* inviteCategory = [[UIMutableUserNotificationCategory alloc] init];
[inviteCategory setActions:#[acceptAction]
forContext:UIUserNotificationActionContextDefault];
inviteCategory.identifier = #"respond";
[categories addObject:inviteCategory];
// Configure other actions and categories and add them to the set...
UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:
(UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound)
categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];}
2) From your Apns server add the category (for me "respond")
{"aps":{"alert":"bla","category":"respond","badge":2}}
3) In your WatchKitExtention, you have the data passed in :
- (void)handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)remoteNotification{
if ([identifier isEqualToString:#"respond"]) {
//Do stuff Here to handle action...
}}
4) In your Parent app's appDelegate :
- (void) application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forRemoteNotification:(NSDictionary *)userInfo
completionHandler:(void (^)())completionHandler {
completionHandler();
}
Warning ! you'll have to handle this action too in your Parent app ( because the respond button will be visible too on iphone when you pan down the notification. in the :
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

Resources