I'm having an issue handling the notification payload on some device. I'm sending push notifications to my users through Parse Cloud functions.
I'm using the below method to capture the notification and storing its payload so that the user can view all the received notifications in a dedicated view. On my personal device I always get the notification and it is saved correctly, on my friend's device though the notification arrive but if the App is in background the payload is not saved, while if the App is in foreground the payload is saved.
Can this be an issue of the device itself? Or maybe something related to the phone provider (I have h3g and he have Vodafone)?
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
// Parse push handler will show a UIAlertView
[PFPush handlePush:userInfo];
if (application.applicationState == UIApplicationStateInactive) {
// tha app is inactive, transitioning to or from the background
completionHandler(UIBackgroundFetchResultNoData);
} else if (application.applicationState == UIApplicationStateBackground) {
// tha app is running in background
// add the notification to the notificationsArrayRecord
NSDate *now = [[NSDate alloc]init];
NSDictionary *aps = userInfo[#"aps"];
NSString *alertMessage = aps[#"alert"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *notificationsArrayRecord = [[defaults arrayForKey:#"notificationsArrayRecord"] mutableCopy];
[notificationsArrayRecord addObject:#[now,alertMessage]];
[defaults setValue: notificationsArrayRecord forKey:#"notificationsArrayRecord"];
// update the notifications counter
NSInteger pushCount = [[NSUserDefaults standardUserDefaults] integerForKey: #"pushCount"];
pushCount ++;
[defaults setInteger: pushCount forKey:#"pushCount"];
[defaults synchronize];
completionHandler(UIBackgroundFetchResultNewData);
} else {
// the app is running in foreground
// add the notification to the notificationsArrayRecord
NSDate *now = [[NSDate alloc]init];
NSDictionary *aps = userInfo[#"aps"];
NSString *alertMessage = aps[#"alert"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *notificationsArrayRecord = [[defaults arrayForKey:#"notificationsArrayRecord"] mutableCopy];
[notificationsArrayRecord addObject:#[now,alertMessage]];
[defaults setValue: notificationsArrayRecord forKey:#"notificationsArrayRecord"];
// update the notifications counter
NSInteger pushCount = [[NSUserDefaults standardUserDefaults] integerForKey: #"pushCount"];
pushCount ++;
[defaults setInteger: pushCount forKey:#"pushCount"];
[defaults synchronize];
completionHandler(UIBackgroundFetchResultNewData);
// refresh the menu buttons and the notification counter
[[NSNotificationCenter defaultCenter] postNotificationName:#"appDidReceiveNotificationWhileActive" object:nil];
}
}
I guess the problem is how you handle the application state UIApplicationStateInactive. In this case, you are not storing the information. You should also store it in this case, because the app can apparently be in this state, when you receive notifications. This also explains, why it fails sometimes.
Also see this question, that states the app is in state UIApplicationStateInactive sometimes, when the device receives a notification.
You should refactor your code to store the data in all cases:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
// Parse push handler will show a UIAlertView
[PFPush handlePush:userInfo];
// add the notification to the notificationsArrayRecord
NSDate *now = [[NSDate alloc]init];
NSDictionary *aps = userInfo[#"aps"];
NSString *alertMessage = aps[#"alert"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *notificationsArrayRecord = [[defaults arrayForKey:#"notificationsArrayRecord"] mutableCopy];
[notificationsArrayRecord addObject:#[now,alertMessage]];
[defaults setValue: notificationsArrayRecord forKey:#"notificationsArrayRecord"];
// update the notifications counter
NSInteger pushCount = [[NSUserDefaults standardUserDefaults] integerForKey: #"pushCount"];
pushCount ++;
[defaults setInteger: pushCount forKey:#"pushCount"];
[defaults synchronize];
if (application.applicationState == UIApplicationStateInactive) {
// the app is inactive, transitioning to or from the background
completionHandler(UIBackgroundFetchResultNoData);
} else if (application.applicationState == UIApplicationStateBackground) {
// the app is running in background
completionHandler(UIBackgroundFetchResultNewData);
} else {
// the app is running in foreground
completionHandler(UIBackgroundFetchResultNewData);
// refresh the menu buttons and the notification counter
[[NSNotificationCenter defaultCenter] postNotificationName:#"appDidReceiveNotificationWhileActive" object:nil];
}
}
Update:
I am not sure about calling completionHandler(UIBackgroundFetchResultNoData) in applicationState (no idea what this is good for), but maybe you need to call completionHandler(UIBackgroundFetchResultNewData) instead, also in this case, to get the data stored.
Also make sure you configured everything properly to receive notifications in background, [see this] answer(https://stackoverflow.com/a/31450953/594074).
Related
I'm currently trying to make a RSS like iPhone application using MWFeedParser https://github.com/mwaterfall/MWFeedParser to parse my xml feed. Currently I'm trying to implement a background fetch into my app. When there are new items the user gets a local notification. I'm having a hard time checking if there are new entries in my RSS feed I'm currently using the code below. parsedItems is an NSArray which is automatically filled when [feedparser parse] is called. Now the problem is that parsedItems.count always returns zero although I refresh my feed. Any ideas?
In my app delegate I have the following function:
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NewsTableViewController *viewController = [[NewsTableViewController alloc] init];
[viewController fetchNewDataWithCompletionHandler:^(UIBackgroundFetchResult result) {
completionHandler(result);
}];
}
And in my NewsTableViewController class I have the function:
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSArray *oldItems;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *objectData = [defaults objectForKey:#"parsedItems"];
if(objectData != nil)
{
oldItems = [NSKeyedUnarchiver unarchiveObjectWithData:objectData];
oldItems = [oldItems sortedArrayUsingDescriptors:
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:#"date"
ascending:NO]]];
}
// Refresh data
[feedParser stopParsing];
[parsedItems removeAllObjects];
[feedParser parse];
NSLog(#"old:%lu", oldItems.count);
NSLog(#"new:%lu", parsedItems.count);
NSLog(#"ding:%lu", self.itemsToDisplay.count);
if (parsedItems.count == oldItems.count) {
completionHandler(UIBackgroundFetchResultNoData);
NSLog(#"No data.");
}
else {
[UIApplication sharedApplication].applicationIconBadgeNumber++;
[self sendNotification];
completionHandler(UIBackgroundFetchResultNewData);
[defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:parsedItems] forKey:#"parsedItems"];
[defaults synchronize];
NSLog(#"New data was fetched.");
}
}
My application is webview.
The normal it load page http://staging.nhomxe.vn.
But when server send a notify, and attachmented a link. Webview will open this link.
My application activity normal. Then, i add Navigation Controller to ViewControler.
When Server send notify, my webview notify error at [vc.webView loadRequest:urlRequest]; , and application auto exit.
My code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound)];
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (localNotif) {
// launched from notification
NSLog(#"Co notify!!!");
NSString *message = [localNotif valueForKey:#"link"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:message forKey:#"LINK"];
} else {
// from the springboard
NSLog(#"Khong co notify!!!");
}
return YES;
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
}
- (void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(#"My token: %#",deviceToken);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:deviceToken forKey:#"TOKEN"];
}
- (void) application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
NSLog(#"Error: %#",error);
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
- (void)application:(UIApplication*)application didReceiveRemoteNotification:
(NSDictionary*)userInfo
{
NSURL *url ;
NSLog(#"Received notification: %#", userInfo);
NSDictionary *data = [ userInfo objectForKey:#"aps"];
for(NSString *key in data) {
NSString *info = [data objectForKey:key];
NSLog(#"thong tin nhan dc: %# : %#", key, info);
}
NSString *message = [userInfo valueForKey:#"link"] ;
//NSArray *info = [message componentsSeparatedByString:#"&#"];
//NSString *body = [info objectAtIndex:0];
//NSString *link = [info objectAtIndex:1];
NSLog(#"Thong tin Link: %#",message);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:message forKey:#"LINK"];
ViewController *vc = (ViewController *)self.window.rootViewController;
if(message == NULL)
{
url = [NSURL URLWithString:#"http://staging.nhomxe.vn"];
}else
{
url = [NSURL URLWithString:message];
}
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[vc.webView loadRequest:urlRequest];
[vc.webView3 loadRequest:urlRequest];
}
#end
My error:
2014-08-28 14:51:59.374 NhomXe[30379:907] Thong tin Link: http://staging.nhomxe.vn/org/instance_message/conversation-detail.xhtml?post=13001628&orgId=190000168#vehicletracking
2014-08-28 14:52:14.950 NhomXe[30379:907] -[UINavigationController webView]: unrecognized selector sent to instance 0x1d5c8350
2014-08-28 14:52:14.958 NhomXe[30379:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController webView]: unrecognized selector sent to instance 0x1d5c8350'
*** First throw call stack:
(0x314e22a3 0x3913e97f 0x314e5e07 0x314e4531 0x3143bf68 0xd687f 0x3353d585 0x3353dfa5 0x33f53305 0x314b7173 0x314b7117 0x314b5f99 0x31428ebd 0x31428d49 0x34fa52eb 0x3333e301 0xd6ad9 0x39575b20)
libc++abi.dylib: terminate called throwing an exception
Your rootViewController is returning a UINavigationController which is used to manage a navigation stack, that is represented by an array of view controllers.
To get your view controller try getting the topViewController, for example:
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
ViewController *vc = (ViewController *)navigationController.topViewController;
You can also get a array of all view controller that navigation manages by:
NSLog(#"%#", navigationController.viewControllers);
Update your UI on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// make sure vc is a weak refernace to avoid retain cycle
[vc.webView loadRequest:urlRequest];
[vc.webView3 loadRequest:urlRequest];
});
I have an app which should only show the settings page once: when the app is opened for the first time.
Now this works, and when the user presses the middle button on the iPhone it then reopens the app and carries on from the main screen - that's great. But if I double click on the iPhone button and swipe the application off, it will then go to the settings screen again and not to where it was.
Why is it doing that? How can I make my app only show its settings once?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSUserDefaults *settingsscreen = [NSUserDefaults standardUserDefaults];
[settingsscreen registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],#"firstTime", nil]];
BOOL firstTime = [settingsscreen boolForKey:#"firstTime"];
if ( firstTime==YES) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"SettingsShown"];
[[NSUserDefaults standardUserDefaults] synchronize];
self.window.rootViewController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"SetUpNav"];
}
else
{
return YES;
}
}
Don't use if ( firstTime==YES) { based on #"firstTime", because that flag is never actually saved. You should be using the flag saved with the #"SettingsShown" key.
BOOL firstTime = [settingsscreen boolForKey:#"SettingsShown"];
if (!firstTime) {
...
Instead doing in appDelegate try to acheive it in setting Page itself
-(void) viewDidLoad {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
username = [defaults objectForKey:#"username"];
if (username != NULL ) {
[self selfLogin];
}
}
-(void)selfLogin{
nextPageController = [[NextPageViewController alloc]init];
[self.navigationController pushViewController:nextPageController animated:YES];
}
I want to use a UISwitch to enable/disable push notifications. Like in Tweetbot.
Does anyone know how to trigger that?
You can also do it in the following way.
create a IBOutlet for UISwitch
#property (strong, nonatomic) IBOutlet *pushNotificationSwitch;
and in Action method, store the value in NSUserDefaults.
- (IBAction)pushNotificationSwitchChanged:(id)sender
{
NSNumber *switch_value = [NSNumber numberWithBool:[self.pushNotificationSwitch isOn]];
[[NSUserDefaults standardUserDefaults] setObject:switch_value forKey:RECIEVE_APNS];
[[NSUserDefaults standardUserDefaults] synchronize];
}
and check it in viewdidload.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSNumber *sett = [[NSUserDefaults standardUserDefaults] valueForKey:RECIEVE_APNS];
if( [sett boolValue] )
{
[self.pushNotificationSwitch setOn:YES];
}
else{
[self.pushNotificationSwitch setOn:NO];
}
}
and In AppDelegate.m, add the following code
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSNumber *sett = [[NSUserDefaults standardUserDefaults] objectForKey:RECIEVE_APNS];
if( [sett boolValue] )
{
int currentBadgeCount = [[NSUserDefaults standardUserDefaults] integerForKey:#"BadgeCount"];
//Set the baadge count on the app icon in the home screen
int badgeValue = [[[userInfo valueForKey:#"aps"] valueForKey:#"badge"] intValue];
[UIApplication sharedApplication].applicationIconBadgeNumber = badgeValue + currentBadgeCount;
[[NSUserDefaults standardUserDefaults] setInteger:badgeValue + currentBadgeCount forKey:#"BadgeCount"];
NSString *alertString = [[userInfo objectForKey:#"aps"] objectForKey:#"alert"];
NSString *playSoundOnAlert = [NSString stringWithFormat:#"%#", [[userInfo objectForKey:#"aps"] objectForKey:#"sound"]];
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#",[[NSBundle mainBundle] resourcePath],playSoundOnAlert]];
NSError *error;
if (alertString.length > 0)
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"App Name" message:alertString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = 1;
[audioPlayer play];
[alert show];
}
}
}
enter code here
You can not do that directly from the application. If you want to do this, you need to make the UISwitch send the information to your backend, store this information in your database and stop sending push notifications to this user.
An app registers for Push Notifications (APN) when it first launches. You cannot have it initialize APNs with a switch once it has already launched. You can however code your app that a switch can choose to do "something" with the user interface once a APN is received.
For example, you can have this code:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSDictionary *apsInfo = [userInfo objectForKey:#"aps"];
NSString *alert = [apsInfo objectForKey:#"alert"];
// do what you need with the data...
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReceivedNotificationAlert" object:self];
}
You can use your UISwitch to either do something, or not, with the NSNotification "ReceivedNotificationAlert". For example:
if(switchAPNprocess.on){
// process APN
}
else {
// ignore APN
}
i have ios project i xcode and I need to get device token from Appdelegate to view controller, here is code of App delegate:
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:#"token"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
And then, in view controller:
[super viewDidLoad];
[[NSUserDefaults standardUserDefaults] objectForKey:#"token"];
When I try it for the first time, it was working, but next time app crahed... When I remove that code from view controller, it works, so it must be wrong there... Can you help me?
First of all, delete your app from your phone/simulator.
Because the NSUserDefaults may hold wrong data for your key.
then replace your code with these,
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
if(deviceToken){
[[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:#"token"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
In viewDidLoad,
- (void)viewDidLoad
{
[super viewDidLoad];
id token = [[NSUserDefaults standardUserDefaults] objectForKey:#"token"];
if(token){
NSLog(#"I have got the token");
}else NSLog(#"no token");
}