I have tried adding in Actionable Notifications for my Parse app, iPrayed 4 U. In the app, someone can "Pray" for you by clicking a button. Doing so runs a Cloud Code that includes a category for the APNS payload. In my AppDelegate I have:
if ([application respondsToSelector:#selector(isRegisteredForRemoteNotifications)])
{UIUserNotificationType types = UIUserNotificationTypeBadge |
UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIMutableUserNotificationAction *acceptAction =
[[UIMutableUserNotificationAction alloc] init];
acceptAction.identifier = #"THANKS_IDENTIFIER";
acceptAction.title = #"Say Thanks";
// Given seconds, not minutes, to run in the background
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
acceptAction.destructive = NO;
acceptAction.authenticationRequired = NO;
UIMutableUserNotificationCategory *inviteCategory =
[[UIMutableUserNotificationCategory alloc] init];
inviteCategory.identifier = #"TAGGED_CATEGORY";
[inviteCategory setActions:#[acceptAction]
forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [NSSet setWithObjects:inviteCategory, nil];
UIUserNotificationSettings *settings =
[UIUserNotificationSettings settingsForTypes:types categories:categories];
[[UIApplication sharedApplication]
registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
}
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forRemoteNotification:(NSDictionary *)notification
completionHandler:(void (^)())completionHandler {
if ([identifier isEqualToString:#"THANKS_IDENTIFIER"]) {
[self handleAcceptActionWithNotification:notification];
NSLog(#"Handled");
}
// Must be called when finished
completionHandler();
}
-(void) handleAcceptActionWithNotification:(NSDictionary *)notification {
NSLog(#"SendingThanks");
PFUser *me = [PFUser currentUser];
NSString *theirname = me[#"additional"];
NSString *myAlertString=notification[#"loc-args"];
NSLog(#"%#", notification);
[PFCloud callFunctionInBackground:#"thankYou"
withParameters:#{#"recipientId": myAlertString, #"theirName": theirname}
block:^(NSString *success, NSError *error) {
if (!error) {
// Push sent successfully
}
}];
}
So, I run to testing. When someone prays for me, I get the notification on my iPhone, drag down and there is the "Say Thanks" button. I click it, but nothing happens. Here is the kicker. Normally I would say, "Well, I messed something up, start debugging". However, I also have an Apple Watch. When I click the Say Thanks button attached to the notification on my Watch, it sends the Push, vs. doing nothing when I click the same button on my iPhone.
Any thoughts as to what is going on here?
UPDATE:
In console, when it fails, I get:
[Error]: The request timed out. (Code: 100, Version: 1.8.2)
2015-10-08 13:42:49.424 iPrayed[2231:712503] [Error]: Network connection failed. Making attempt 1 after sleeping for 1.463493 seconds.
Eventually I get a success call, but by that time it still doesn't actually send the Push. Any idea why it is happening only when tapped from iPhone and not watch?
Because you call completionHandler() before PFCloud finished. You need call completionHandler() in completionBlock
Do this.
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)notification completionHandler:(void (^)())completionHandler {
if ([identifier isEqualToString:#"THANKS_IDENTIFIER"]) {
PFUser *me = [PFUser currentUser];
NSString *theirname = me[#"additional"];
NSString *myAlertString=notification[#"loc-args"];
[PFCloud callFunctionInBackground:#"thankYou" withParameters:#{#"recipientId": myAlertString, #"theirName": theirname} block:^(NSString *success, NSError *error) {
completionHandler();
}];
} else {
completionHandler();
}
}
Related
I have a button in my app that triggers registering for notifications. Say this is a UIBarButton.
UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:#"Grant" style:UIBarButtonItemStyleDone target:self action:#selector(grantPermission:)];
self.navigationItem.rightBarButtonItem = rightButton;
This is the function the button triggers:
- (void)grantPermission:(id)sender {
if (SYSTEM_VERSION_LESS_THAN( #"10.0" )) {
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[[UIApplication sharedApplication] registerForRemoteNotifications];
} else {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
NSLog(#"Completion handler called");
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] registerForRemoteNotifications];
});
} else {
NSLog( #"Push registration FAILED" );
NSLog( #"ERROR: %# - %#", error.localizedFailureReason, error.localizedDescription );
NSLog( #"SUGGESTIONS: %# - %#", error.localizedRecoveryOptions, error.localizedRecoverySuggestion );
}
}];
}
}
I also added the following delegate methods:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(#"Did Register for Remote Notifications with Device Token (%#)", deviceToken);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(#"Did Fail to Register for Remote Notifications");
NSLog(#"%#, %#", error, error.localizedDescription);
}
When I run this code, I get the modal asking for permission. When I press 'accept' I see the Completion handler called log, but nothing after that. Nothing happens, no logs are shown. Nothing. Any ideas as to what I'm doing wrong?
You must test that code on a physical device. Simulators don't get notifications token. You will get an error callback.
Also check which version of iOS you are targeting because the callbacks in the app delegate which changed between ios9-10 (possibly 11) so you will have a deprecated method which won't get called (or visa versa depending on which version of iOS you are testing against)
I have configured an action for a remote notification when it arrives to my iOs app. I want two different actions whether one key passed in the payload. The code seems to be executed, but the app doesn't open, nor the safari url. Here is my AppDelegate.m:
NSString *const NotificationCategoryOpenView = #"openView";
NSString *const NotificationActionOpenView = #"View";
- (void)registerForNotification {
UIApplication *application = [UIApplication sharedApplication];
// iOs 8 or greater:
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
UIMutableUserNotificationAction *open;
open = [[UIMutableUserNotificationAction alloc] init];
[open setActivationMode:UIUserNotificationActivationModeBackground];
[open setTitle:NSLocalizedString(#"View", nil)];
[open setIdentifier:NotificationActionOpenView];
[open setDestructive:NO];
[open setAuthenticationRequired:NO];
UIMutableUserNotificationCategory *actionCategory;
actionCategory = [[UIMutableUserNotificationCategory alloc] init];
[actionCategory setIdentifier:NotificationCategoryOpenView];
[actionCategory setActions:#[open]
forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [NSSet setWithObject:actionCategory];
UIUserNotificationType types = (UIUserNotificationTypeAlert|
UIUserNotificationTypeSound|
UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings;
settings = [UIUserNotificationSettings settingsForTypes:types
categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
} else if ([application respondsToSelector:#selector(registerForRemoteNotificationTypes:)]) {
// iOs 7 or lesser:
UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
[application registerForRemoteNotificationTypes:myTypes];
}
}
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler {
if ([identifier isEqualToString:NotificationActionOpenView]) {
NSDictionary *aps = userInfo[#"aps"];
if ([[aps allKeys] containsObject:#"viewToOpen"]){
NSString *webString = [[NSString alloc] initWithFormat:#"%#", aps[#"viewToOpen"]];
NSURL *webURL = [[NSURL alloc] initWithString:webString];
// This line doesn't work:
[[UIApplication sharedApplication] openURL:webURL];
} else {
// These two lines doesn't work:
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
[navigationController popToRootViewControllerAnimated:NO];
}
}
completionHandler();
}
Thanks!
Your code should be in this method
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
if(userInfo) {
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
NSUInteger notificationType = [[userInfo valueForKey:API_NOTIFICATION_TYPE] integerValue];
if (state == UIApplicationStateActive) {
// Write code which should work when notification comes and app is active
}
else {
// Write code which should work when notification comes and app is in background or inactive or terminated
}
}
}
}
This method will get called only
when app is ACTIVE and notification comes
when app is in background or inactive or terminated and notifications comes and user taps on it
Implement
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler
And
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
Methods.
Notification activation mode is set to UIUserNotificationActivationModeBackground,which Activate the app and put it in the background. If the app is already in the foreground, it remains in the foreground.So if you want to application in foreground/launch then use UIUserNotificationActivationModeForeground
[open setActivationMode:UIUserNotificationActivationModeBackground];
Use this activation option:
[open setActivationMode: UIUserNotificationActivationModeForeground];
Check weather webUrl is proper and working as expected in normal flow.
For Xcode 8, u will have to migrate push notification using UserNotification framework.
I use APNs to send the notifications to my app. But my app does not work well when I did the following steps:
steps
swipe the app to force quit (app is not running, not in background mode ..)
send the notification from APNs
got the notification on my iPhone and I tapped the notification banner
app seemed to try to launch(showed the launch image), but launched fail (crash?)
my app can receive notification foreground and background.
Tap the notification banner in background then it can bring app to foreground then go to the view I wrote, everything works fine.
Except force quit the APP
here is my code in AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MFSideMenuContainerViewController *container = [MFSideMenuContainerViewController
containerWithCenterViewController:[self navigationController]
leftMenuViewController:leftMenuViewController
rightMenuViewController:rightMenuViewController];
self.window.rootViewController = container;
[self.window makeKeyAndVisible];
UIMutableUserNotificationAction *acceptAction =
[[UIMutableUserNotificationAction alloc] init];
// Define an ID string to be passed back to your app when you handle the action
acceptAction.identifier = #"MARK_AS_READ_IDENTIFIER";
// Localized string displayed in the action button
acceptAction.title = NSLocalizedString(#"Mark as Read", nil);
// If you need to show UI, choose foreground
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
// Destructive actions display in red
acceptAction.destructive = NO;
// Set whether the action requires the user to authenticate
acceptAction.authenticationRequired = NO;
// First create the category
UIMutableUserNotificationCategory *inviteCategory =
[[UIMutableUserNotificationCategory alloc] init];
// Identifier to include in your push payload and local notification
inviteCategory.identifier = #"actionCategory";
// Add the actions to the category and set the action context
[inviteCategory setActions:#[acceptAction]
forContext:UIUserNotificationActionContextDefault];
// Set the actions to present in a minimal context
[inviteCategory setActions:#[acceptAction]
forContext:UIUserNotificationActionContextMinimal];
NSSet *categories = [NSSet setWithObject:inviteCategory];
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert) categories:categories]];
// for calling didReceiveRemoteNotification when app first launch
if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
[self application:application didReceiveRemoteNotification:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
}
return YES;
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
//register to receive notifications
[application registerForRemoteNotifications];
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(#"Device token: %#",deviceToken);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(#"Fail to get device token: %#", error);
}
// tap the backgraund banner button
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)newUserInfo completionHandler:(void (^)())completionHandler {
if ([identifier isEqualToString:#"MARK_AS_READ_IDENTIFIER"]) {
// when tapping the background banner's button will mark the notification status to read
[Functions updateComingNotificationToRead];
}
if (completionHandler) {
completionHandler();
}
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)newUserInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))handler {
//NSLog(#"Notification received: %# %#", newUserInfo,[newUserInfo objectForKey:#"aps"] );
userInfo = newUserInfo;
NSString *alertMessage = [[newUserInfo objectForKey:#"aps"] objectForKey:#"alert"];
UIApplicationState state = [application applicationState];
UIAlertView *alertView = nil;
// for background banner use
switch (state) {
case UIApplicationStateActive: // when app is alive, show alert to notify user
alertView = [[UIAlertView alloc]initWithTitle:NSLocalizedString(#"SmartHome",nil) message:NSLocalizedString(alertMessage,nil) delegate:self cancelButtonTitle:NSLocalizedString(#"Close",nil) otherButtonTitles:NSLocalizedString(#"Open",nil),NSLocalizedString(#"Mark as Read",nil), nil];
[alertView show];
break;
case UIApplicationStateBackground: // app is in background mode
// user tap the banner or tap the mark as read button, code will go here
[Functions addNotificationDataInDatabase:[newUserInfo objectForKey:#"uniqueID"] type:[newUserInfo objectForKey:#"deviceType"] event:[newUserInfo objectForKey:#"event"] time:[newUserInfo objectForKey:#"time"] read:#"0" description:alertMessage];
break;
case UIApplicationStateInactive: // tapping the banner
//NSLog(#"UIApplicationStateInactive");
// go to notification view
// because will go to the notification view detail, set status to read
[self gotoNotificationView:userInfo]; //uniqueID
break;
default:
break;
}
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
// Handle the received message
// Invoke the completion handler passing the appropriate UIBackgroundFetchResult value
handler(UIBackgroundFetchResultNoData);
// send post notification for updating the badge, will get the notification in foreground
[[NSNotificationCenter defaultCenter] postNotificationName:#"APNsNotification" object:self userInfo:nil];
}
Does anyone have this problem before? Did I miss something?
Please help me!!
u can put alert and check launchOptions
if (launchOptions) {
if ([launchOptions objectForKey:#"UIApplicationLaunchOptionsRemoteNotificationKey"]) {
[self application:self didReceiveRemoteNotification:[launchOptions objectForKey:#"UIApplicationLaunchOptionsRemoteNotificationKey"] fetchCompletionHandler:^(UIBackgroundFetchResult result) {
}];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)newUserInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))handler {
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) {
NSString *str = [[userInfo objectForKey:#"aps"] objectForKey:#"alert"];
}
}
as apple suggested use Handoff in Glance .
I wants to call web API in Glance Interface , for this I did following things
- (void)awakeWithContext:(id)context
{
[super awakeWithContext:context];
[self CreateUaerActivity];
}
-(void)CreateUaerActivity
{
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:#"com.xxx.xxx.glance"];
activity.title = #"Glance";
activity.delegate=self;
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:kUserLoginWatchKit,kRequestTypeWatchKit, nil];
activity.userInfo = dict;
self.userActivity = activity;
[self.userActivity becomeCurrent];
}
- (void)willActivate
{
[super willActivate];
[NSTimer scheduledTimerWithTimeInterval:120 target:self selector:#selector(doSomething) userInfo:nil repeats:YES];
}
-(void)doSomething
{
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:kUserLoginWatchKit,kRequestTypeWatchKit, nil];
[super updateUserActivity:#"com.xxx.xxx.glance" userInfo:dict webpageURL:nil];
}
-(void)handleUserActivity:(NSDictionary *)userInfo
{
//displaying data
}
and in AppDelegate.m file -
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler
{
NSLog(#"Handoff dictionary: %#", userActivity.userInfo);
NSString *requestType = userActivity.userInfo[kRequestTypeWatchKit];
if ([requestType isEqual: kGlanceDataWatchKit])
{
//calling web API to get Data
}
return YES;
}
I found AppDelegate never called continueUserActivity method to return something to Glance interface.
please guide me how to call API through Glance Interface.
I'm not sure if this is what you want, but if you want to call an web Api i suggest yout to do it like this :
in the GlanceInterfaceController :
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:#"getSomething" forKey:#"action"];
[MainInterfaceController openParentApplication:dictionary reply:^(NSDictionary *replyInfo, NSError *error) {
NSLog(#"Reply received by Watch app: %#", replyInfo); // the reply from the appDelegate...
}
in your parent's app Delegate :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
NSLog(#"Request received by iOS app");
if( [userInfo objectForKey:#"action"] isEqualToString:#"getSomething"] ){
//call you're Web API
//send the reponse to you're glance :
reply(DictResponse);// some Dictionary from your web API...
}
*****EDIT*****
i've been issued the same issue, one easy fix is to begin an background task, from :
fiveminutewatchkit
Here's the way :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
// Temporary fix, I hope.
// --------------------
__block UIBackgroundTaskIdentifier bogusWorkaroundTask;
bogusWorkaroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
});
// --------------------
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
// Kick off a network request, heavy processing work, etc.
// Return any data you need to, obviously.
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}
in fact iOS kill your parent's app before you can retrieve data... this (not very clean solution) prevent you're app to be killed... and let you the time to retrieve infos...
******END EDIT******
I have the following code that I use for push notifications:
located within my appdelegate.m is the following code
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
//Register to receive notifcations
//-- Set Notification
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
else
{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)];
}
[Pushbots getInstance];
NSDictionary * userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(userInfo) {
// Notification Message
NSString* notificationMsg = [userInfo valueForKey:#"message"];
// Custom Field
NSString* title = [userInfo valueForKey:#"title"];
NSLog(#"Notification Msg is %# and Custom field title = %#", notificationMsg , title);
}
return YES;
}
-(void)onReceivePushNotification:(NSDictionary *) pushDict andPayload:(NSDictionary *)payload {
[payload valueForKey:#"title"];
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"New Alert !" message:[pushDict valueForKey:#"alert"] delegate:self cancelButtonTitle:#"Thanks !" otherButtonTitles: #"Open",nil];
[message show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"Open"]) {
[[Pushbots getInstance] OpenedNotification];
// set Badge to 0
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
// reset badge on the server
[[Pushbots getInstance] resetBadgeCount];
}
}
This code works fine when the application is not closed, it presents me with the notification alert view.
HOWEVER it does not work properly when the application is closed fully and not running in background.
And I dont know what to do about it!
Thanks in advance?
you cannot since displaying an alert view required the notification invoking some part of your code, however the entry point for your code i.e. where you may first handle anything regarding your notification is on
(BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary) launchOptions
which means your app has to be awake in order to show a custom alert view.
For now you will have to settle to the default implementation ios gives you, that is the notification bar at the top.
hope ts useful