Unable to get push notifications on all devices - ios

I am using the APNS for receiving push notifications in my application.
The problem is that I am getting the same notifications on some devices but not all. What could the problem be here since I have been trying for about 15 days to solve this issue with no success. Th device token is updating successfully. Because had it not been then I wouldn't have been getting notification on any device. But the strange thing is I am getting it on half the devices. Please help!!
Here is the code for registering and receiving notifications. I don't have any code for server side. But as I said that the notifications are working on some devices. On android too they are working.
I have 3 devices here with me and its working on two of them.
iPad2:5.0.1
iPodTouch:4.3.3
Its not working on another iPod touch that I have on version:5.1
Also its showing successfully registered for APNS in all devices. But not sending notifications on some. What can the issue be? Is there something that I am missing?
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
{
deviceToken = [devToken retain];
NSLog(#"Registered for APNS %#", deviceToken);
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableString *dev = [[NSMutableString alloc] init];
NSRange r;
r.length = 1;
unsigned char c;
for (int i = 0; i < [deviceToken length]; i++)
{
r.location = i;
[deviceToken getBytes:&c range:r];
if (c < 10) {
[dev appendFormat:#"0%x", c];
}
else {
[dev appendFormat:#"%x", c];
}
}
[ud setObject:dev forKey:#"DeviceToken"];
[ud synchronize];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
NSLog(#"Failed to register %#", [error localizedDescription]);
deviceToken = nil;
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
for(int i=0;i<[viewControllers count];i++)
{
if([[viewControllers objectAtIndex:i] isKindOfClass:[Confirmation class]])
{
Confirmation *map = (Confirmation*)[[self.navigationController viewControllers] objectAtIndex:i];
[map setFinalInfo];
[self.navigationController popToViewController:[[self.navigationController viewControllers] objectAtIndex:i] animated:YES];
}
}
}

1 ) There is no guarantee that push notifications will actually be delivered, even if the APNS server accepted them.
2 ) As far as your server is concerned, push notifications are fire-and-forget; there is no way to find out what the status of a notification is after you’ve sent it to APNS. The delivery time may also vary, from seconds up to half an hour.
3 ) Also, the user’s iPhone may not be able to receive push notifications all the time. They could be on a WiFi network that does not allow connections to be made to APNS because the required ports are blocked. Or the phone could be turned off.
4 ) APNS will try to deliver the last notification it received for that device when it comes back online, but it will only try for a limited time. Once it times out, the push notification will be lost forever!

Are you setting the "expiry" to 0 in the APN message? If you set it to 0 push messages are try-once-and-forget, else Apple may try to deliver them till the time specified.
The other thing to check might be to be absolutely certain no device id's from your development test runs end up in the list of device id's you are actually going to send push messages in production. One faulty device id and Apple will cease the SSL connection and not process any more APN messages.

I replaced the following code:
deviceToken = [devToken retain];
NSLog(#"Registered for APNS %#", deviceToken);
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableString *dev = [[NSMutableString alloc] init];
NSRange r;
r.length = 1;
unsigned char c;
for (int i = 0; i < [deviceToken length]; i++)
{
r.location = i;
[deviceToken getBytes:&c range:r];
if (c < 10) {
[dev appendFormat:#"0%x", c];
}
else {
[dev appendFormat:#"%x", c];
}
}
[ud setObject:dev forKey:#"DeviceToken"];
[ud synchronize];
By this:
[devToken retain];
NSLog(#"~~~~devToken=%#",devToken);
NSString *dt = [[devToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
dt = [dt stringByReplacingOccurrencesOfString:#" " withString:#""];
//    //DeviceToken = dt;
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
[def setObject:dt forKey:#"DeviceToken"];
[def synchronize];
and its working fine now!!
The problem was it wasnt updating the device toekn correctly for some devices which I am not sure why as it was working well for some.
Thanks guys!!

Related

Notification iOS 13+ and JavaPNS - Invalid token

I use JavaPNS (2.2) for send notification.
Now, with iOS 13.x, i have a problem.
This is my (new) code for register the device to server:
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *tokenNotification = nil;
NSUInteger length = deviceToken.length;
if(length > 0) {
const unsigned char *buffer = deviceToken.bytes;
NSMutableString *hexString = [NSMutableString stringWithCapacity:(length * 2)];
for(int i=0; i<length; ++i) {
[hexString appendFormat:#"%02x", buffer[i]];
}
tokenNotification = hexString;
}
...
...
...
}
tokenNotification has same value of deviceToken.debugDescription.
When I try to send notification using JavaPNS, returns an error: "Invalid token".
I have this problem only on iOS 13+.
My device has iOS 13.7.
With iOS up to 12, using deviceToken as value of registration (without "<" and spaces), works fine.
Can you help me?
Ok, solved!
It was just a certificate issue. :)

Get device token for iOS push notifications at certain point in app

I've tried Googling, and keep hitting the same sorts of answers, ie.
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
token = [token stringByReplacingOccurrencesOfString:#" " withString:#""];
NSLog(#"content---%#", token);
}
However, I'm struggling to see how I might call/invoke this method in my App Delegate at a given event.
In my app, the first screen they see is login/signup. So on success of signup, I'd like to grab the device token and send it to my server to save against that new user. How exactly can I invoke this method from within a View Controller (or wherever) at a given time/event of my choosing, (and not just when the app first boots up)?
Hi try to use NSUserDefaults to store this data.
[[NSUserDefaults standardUserDefaults] setValue:deviceTokenString forKey:#"deviceToken"];
And to get this data use:
NSUserDefaults *data = [NSUserDefaults standardUserDefaults];
NSString *string = [data objectForKey:#"deviceToken"];

Getting the Device ID before running ViewController code in IOS

I have this peace of code to get device id :
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSString *devicetokenRN = [NSString stringWithFormat:#"%#", deviceToken];
devicetokenRN = [devicetokenRN stringByReplacingOccurrencesOfString:#" " withString:#""];
devicetokenRN = [devicetokenRN stringByReplacingOccurrencesOfString:#"<" withString:#""];
devicetokenRN = [devicetokenRN stringByReplacingOccurrencesOfString:#">" withString:#""];
// send device token for APNS without spaces or <>
DeviceID *DeviceIdentifier = [[DeviceID alloc ] init];
// sending it to the Device.h
[DeviceIdentifier setDeviceID:devicetokenRN];
}
I need to retrieve this DeviceID as the first thing on my ViewController. As it doesn't do this function before it runs the view controller, because if I use NSlog in the ViewController it is NULL as it hasn't run this first. I need to get some variable like this first before it runs the UITableView. Any ideas would be brilliant thanks.
In AppDelegate, you can save the deviceToken value in NSUserDefaults like
put these line in didRegisterForRemoteNotificationsWithDeviceToken method in appdelegate.m like this
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:#"DeviceToken"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
also put this line of code in your view Controller.m and In ViewDidLoad method
and Retrieve that value.
[[NSUserDefaults standardUserDefaults] objectForKey:#"DeviceToken"];
You can get and store your deviceId in NSUserDefaults
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSUUID *identifierForVendor = [[UIDevice currentDevice] identifierForVendor];
NSString *deviceId = [identifierForVendor UUIDString];
NSUserDefaults *userRegistrarionDetails = [NSUserDefaults standardUserDefaults];
[userRegistrarionDetails setObject:deviceId forKey:#"deviceID"];
[userRegistrarionDetails synchronize];
}
And you can get anywhere in your whole project by
NSUserDefaults *userRegistrarionDetails = [NSUserDefaults standardUserDefaults];
NSString * deviceId = [userRegistrarionDetails objectForKey:#"deviceID"];
NSLog(#"%#", deviceId);

CFUUID stored in SSKeychain is null in some devices

My published application makes use of the CFUUID and SSKeychain in order to identify the device (and to keep that ID unchanged even if the app is uninstalled and reinstalled)
I save those device ID in the server, and I recently noticed that some users have several of those IDs for the same real device. The only explanation I see is that the ID is not being saved or loaded from the Keychain and thus the device generates a new one. The strange thing is that it works fine on some other devices running the same iOS version.
Any ideas on what could be going on?
This is my related code in (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSString* identifier = #"appName";
NSString* serviceName = #"com.company.appName";
NSString *retrieveuuid = [SSKeychain passwordForService:serviceName account:identifier];
if (retrieveuuid == nil) {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
NSString *uuid = (NSString*) string;
[SSKeychain setPassword:uuid forService:serviceName account:identifier];
}
EDIT: My guess is that retrieveuuid == nil is not working as expected for some reason. Later in the app I register for push notifications and send the push token to the server together with this CFUUID that I read with the same exact line [SSKeychain passwordForService:serviceName account:identifier] but when it is sent to the server it is not nil (So I can see several CFUUIDs with the same push token).
EDIT 2 to attach more actual code.
AppDelegate.m
NSString* identifier = #"appName";
NSString* serviceName = #"com.company.appName";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Creating UUID
NSString *retrieveuuid = [AppDelegate getDeviceId];
if (retrieveuuid == nil) {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
NSString *uuid = (NSString*) string;
[SSKeychain setPassword:uuid forService:serviceName account:identifier];
}
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
}
+ (NSString*) getDeviceId {
return [SSKeychain passwordForService:serviceName account:identifier];
}
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
NSString *newToken = [deviceToken description];
newToken = [newToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
newToken = [newToken stringByReplacingOccurrencesOfString:#" " withString:#""];
_deviceToken = [[NSString alloc] initWithString:newToken];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *user = [prefs objectForKey:#"appNameUsername"];
if(user && ![user isEqualToString:#""]){
RestClient *rest = [[RestClient alloc] init];
rest.delegate = self;
rest.tag = 2;
[rest updateToken:newToken ForUsername:user];
[rest release];
}
}
RestClient.m
-(void) updateToken:(NSString *)token ForUsername:(NSString *)userName{
NSArray* info = [NSArray arrayWithObject: [NSMutableDictionary dictionaryWithObjectsAndKeys:
userName, #"Username",
token, #"TokenNo",
[[UIDevice currentDevice].model hasPrefix:#"iPad"] ? #"iPad" : #"iPhone", #"Device",
[UIDevice currentDevice].systemVersion, #"OSVersion",
[AppDelegate getDeviceId], #"DeviceID",
#"updateToken", #"CMD",
nil]];
[self doAction:info];
}
The doAction method just sends the data to the server and then callback the delegate, that part works fine. I can see on the server the logs of receiving this command:
"JSONCMD":"[
{ "TokenNo" : "f0d3aa21758350333b7e6315c38_EDIT_257c1838f49c43049f8380ec1ff63",
"AppVersion" : "1.0.4",
"Username" : "user#server.com",
"CMD" : "updateToken",
"OSVersion" : "7.0.4",
"DeviceID" : "9B794E11-6EF7-470C-B319-5A9FCCDAFD2B",
"Device" : "iPhone"
}
]
I see 2 possible candidates causing the strange behaviour, the NSStrings in the controller body and the static getDevice method. However, I don't see how this could work in many devices but fail in others.
I've had the same problem and I've found the solution. The Problem occurs with devices where user has the passcode set to unlock the phone. On iOS7 some code can run in the background (push notifications, background fetch) and if you save something in the keychain with standard accessibility type, you cannot read it when the device is locked (and the app is running in the background). Try to set this:
[SSKeychain setAccessibilityType:kSecAttrAccessibleAlways];
before you read/write the keychain.
Hope it helps.
NSString *retrieveuuid = [SSKeychain passwordForService:serviceName
account:identifier];
The retrieveuuid depends with the service, witch is always the same thing, but it depends also with the account. I think your problem is that one user can have multiple accounts in the same device, then your code are generating a UUID for each account.

didReceiveRemoteNotification:fetchCompletionHandler not being called when app is in background and not connected to Xcode

I've a very strange problem, I implemented:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
For silent remote push notification.
It works perfect when app is in background and connected to Xcode.
When I unplug any iOS device and run the app, move to background and send remote notification, didReceiveRemoteNotification:fetchCompletionHandler not being called.
My code below:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSInteger pushCode = [userInfo[#"pushCode"] integerValue];
NSLog(#"Silent Push Code Notification: %i", pushCode);
NSDictionary *aps = userInfo[#"aps"];
NSString *alertMessage = aps[#"alert"];
if (pushCode == kPushCodeShowText) {
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
localNotif.fireDate = [NSDate date];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.alertBody = alertMessage;
localNotif.alertAction = #"OK";
localNotif.soundName = #"sonar.aiff";
// localNotif.applicationIconBadgeNumber = 0;
localNotif.userInfo = nil;
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotif];
UILocalNotification *clearNotification = [[UILocalNotification alloc] init];
clearNotification.fireDate = [NSDate date];
clearNotification.timeZone = [NSTimeZone defaultTimeZone];
clearNotification.applicationIconBadgeNumber = -1;
[[UIApplication sharedApplication] presentLocalNotificationNow:clearNotification];
}
else if (pushCode == kPushCodeLogOut) {
[[MobileControlService sharedService] logoutUser];
[[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
}
else if (pushCode == kPushCodeSendLocation) {
[[MobileControlService sharedService] saveLocation];
}
else if (pushCode == kPushCodeMakeSound) {
[[MobileControlHandler sharedInstance] playMobileControlAlertSound];
// [[MobileControlHandler sharedInstance] makeAlarm];
[[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
}
else if (pushCode == kPushCodeRecordAudio) {
if ([MobileControlHandler sharedInstance].isRecordingNow) {
[[MobileControlHandler sharedInstance] stopRecord];
} else {
[[MobileControlHandler sharedInstance] startRecord];
}
[[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
}
completionHandler(UIBackgroundFetchResultNewData);
}
- (void)saveLocation {
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
char *hostname;
struct hostent *hostinfo;
hostname = "http://m.google.com";
hostinfo = gethostbyname(hostname);
if (hostname == NULL) {
NSLog(#"No internet connection (saveLocation)");
return;
}
if (self.locationManager.location.coordinate.latitude == 0.0 || self.locationManager.location.coordinate.longitude == 0.0) {
NSLog(#"saveLocation - coordinates are 0.0.");
return;
}
NSLog(#"saveLocation - trying to get location.");
NSString *postBody = [NSString stringWithFormat:#"Lat=%#&Lon=%#&Date=%#&userID=%#&batteryLevel=%#&version=%#&accuracy=%#&address=%#", self.myInfo.lat, self.myInfo.lon, self.myInfo.date, self.myInfo.userID, self.myInfo.batteryLevel, self.myInfo.version, self.myInfo.accuracy, self.myInfo.address];
NSURL *completeURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#/saveLocation", WEB_SERVICES_URL]];
NSData *body = [postBody dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:completeURL];
[request setHTTPMethod:#"POST"];
[request setValue:kAPP_PASSWORD_VALUE forHTTPHeaderField:kAPP_PASSWORD_KEY];
[request setHTTPBody:body];
[request setValue:[NSString stringWithFormat:#"%d", body.length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
if (__iOS_7_And_Heigher) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"saveLocation Error: %#", error.localizedDescription);
} else {
NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"\n\nResponseXML(saveLocation):\n%#", responseXML);
[self cloudAcknowledge_whoSend:kPushCodeSendLocation];
}
}];
[dataTask resume];
}
else {
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
NSLog(#"saveLocation Error: %#", connectionError.localizedDescription);
} else {
NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"\n\nResponseXML(saveLocation):\n%#", responseXML);
[self cloudAcknowledge_whoSend:kPushCodeSendLocation];
}
}];
}
}
- (void)startBackgroundTask {
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
}
- (void)endBackgroundTask {
if (bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
And [self endBackgroundTask] is at the end of cloudAcknowledge function.
Any idea what the hell is going on here?
EDIT:
Payload goes like this:
{ aps = { "content-available" = 1; }; pushCode = 12; }
There could be number of things might have gone wrong, The first from my own experience. In order to make silent push notification work. Your payload has to be structured correctly,
{
"aps" : {
"content-available" : 1
},
"data-id" : 345
}
Does your push message has content-available: 1 if not then iOS will not call the new delegate method.
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
Possible reason is that Background App Refresh is off on your iPhone.
You can turn this option on/off in Settings->General->Background App Refresh.
When Background App Refresh is off on your phone, didReceiveRemoteNotification:fetchCompletionHandler method will be called only when the phone is connected to XCode.
Just want to add an updated answer.
I am facing the same problem.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
Doesn't get called when the app is killed from background multitasking (double tap home button and swipe up to kill app).
I have tested this myself using development push notification and NWPusher tool (https://github.com/noodlewerk/NWPusher)
Outdated documentation
This previous block of documentation which says:
Unlike the application:didReceiveRemoteNotification: method, which is
called only when your app is running, the system calls this method
regardless of the state of your app. If your app is suspended or not
running, the system wakes up or launches your app and puts it into the
background running state before calling the method. If the user opens
your app from the system-displayed alert, the system calls this method
again so that you know which notification the user selected.
Is outdated (at the time of writing this 04/06/2015).
Updated Documentation (as at of 04/06/2015)
I checked the documentation (at the time of writing this 04/06/2015), it says:
Use this method to process incoming remote notifications for your app.
Unlike the application:didReceiveRemoteNotification: method, which is
called only when your app is running in the foreground, the system
calls this method when your app is running in the foreground or
background. In addition, if you enabled the remote notifications
background mode, the system launches your app (or wakes it from the
suspended state) and puts it in the background state when a remote
notification arrives. However, the system does not automatically
launch your app if the user has force-quit it. In that situation, the
user must relaunch your app or restart the device before the system
attempts to launch your app automatically again.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:fetchCompletionHandler:
If you read carefully, you'll notice it now says:
the system
calls this method when your app is running in the foreground or
background.
NOT:
regardless of the state of your app
So it looks like from iOS 8+ we're out of luck :(
TL;DR: Use Test Flight in iTunes Connect
Maybe some of you guys already figured out this, but I posting here since I don't see a clear answer.
The had the exact same problem describe. Silent push notifications worked while the Lightning cable was connected. Stopped working when I disconnected the cable. I had every NSLog and network call tracked to prove that was indeed happening.
I was using the payload suggested in many answers, as well as this one:
{
"aps" : {
"content-available": 1,
sound: ""
}
}
After many hours, I discovered that the issue is related to Adhoc and Development provisioning profiles, on iOS 8.0, 8.1 and 8.1.1. I was using Crashlytics to send beta versions of my app (that uses Adhoc profile).
The fix is:
In order to have it working, try out Apple's Test Flight integration with iTunes Connect. Using that you will send an Archive of your app (the same archive to be used on App Store) but enable your binary to be used in beta. The version installed from Test Flight probably (I can't prove) uses the same Production Provisioning Profile from the App Store, and the app works like a charm.
Here's a link that helps set up the Test Flight account:
http://code.tutsplus.com/tutorials/ios-8-beta-testing-with-testflight--cms-22224
Not even a single Silent Payload was missed.
I hope that helps someone not to lose a whole week on this issue.
This was an issue for me today and I was baffled.
iOS: 10.2.1
xCode: 8.2.1
Swift: 3.0.2
The issues was only on one phone I would get the packed only when plugged into xCode.
I re-read Apples push documentation in case I missed something with the new UserNotifications framework and or messed something up with my code to fall back to the depreciated delegate functions for iOS 9.
Anyway, I noticed this line in the documentation for application:didReceiveRemoteNotification:fetchCompletionHandler::
"Apps that use significant amounts of power when processing remote notifications may not always be woken up early to process future notifications."
It's the very last line on the page.
While I wish Apple told me more, it turns out a simple phone restart solved the problem for me. I really wish I could figure out exactly what went wrong, but here are my very speculative conclusions:
1) Push notifications were not being delivered to this app on this particular phone because of the line in the documentation mentioned above.
2) When plugged into xCode iOS is ignoring the above, documented rule.
3) I checked the (notoriously confusing) battery percentage calculator in system settings. It showed my app at a modest 6%, BUT Safari was a whopping 75% on this phone for some reason.
4) After phone restart, Safari was back down to about 25%
5) Push worked fine after that.
So... My ultimate conclusion. To weed out the documented battery issue either try a phone restart or try a different phone and see if the problem persists.
To use Background Push Download in iOS application development, here are some important points which we need to follow…
Enable UIBackgroundModes key with remote-notification value in info.plist file.
Then implement below method in your AppDelegate file.
application:didReceiveRemoteNotification:fetchCompletionHandler
More Details:ios-7-true-multitasking
Spent two days on this! Before checking your code and your push params - check that you are not on LOW POWER MODE!!!(and Background App Refresh is ON)
as you connect your device to xCode==power it will work, but if you will disconnect it - low power mode will disable background app refresh.
It is very simple. You can call your method
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler {
}
Steps:
project -->Capablities--> Background Modes
and select check boxes of "Background Fetch" & "Remote notifications", or go into .plist and select a row & give name "Background Modes" and it will create with an array, set "Item 0" with string "Remote notifications".
say to server side developer that he should send
"aps" : {
"content-available" : 1
}
thats it now you can call your methods:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler {
}
Issue have been fixed in iOS 7.1 Beta 3.
I double checked and I confirm it's working just fine.
Code that works fetching remote notifications, enable te remote notifications capability in background modes and i have background fetch enabled too (i don't know if it is necessary) I use this code:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler{
DLog(#"899- didReceiveRemoteNotification userInfo: %#",userInfo);
NSDictionary *custom=userInfo[#"custom"];
if(custom){
NSInteger code = [custom[#"code"] integerValue];
NSInteger info = [custom[#"info"] integerValue];
NSDictionary *messageInfo = userInfo[#"aps"];
[[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground];
handler(UIBackgroundFetchResultNewData);
}
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
DLog(#"899- didReceiveRemoteNotification userInfo: %#",userInfo);
NSDictionary *custom=userInfo[#"custom"];
if(custom){
NSInteger code = [custom[#"code"] integerValue];
NSInteger info = [custom[#"info"] integerValue];
NSDictionary *messageInfo = userInfo[#"aps"];
[[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground];
}
}
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
//NSLog(#"My token is: %#", deviceToken);
const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes];
NSString *hexToken = [NSString stringWithFormat:#"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
[[eInfoController singleton] setPushNotificationToken:hexToken];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
NSLog(#"Failed to get token, error: %#", error);
}
Code that stores the notification when it background, the key for me was to start a background download task to allow me to download the information in order to store it and then when app becomes active method is triggered i check if there is a missing notification stored to show it.
-(void)remoteNotificationReceived:(NSInteger)code info:(NSInteger)info messageInfo:(NSDictionary*)messageInfo appInBackground:(BOOL)appInBackground{
DLog(#"Notification received appInBackground: %d,pushCode: %ld, messageInfo: %#",appInBackground, (long)code,messageInfo);
switch (code){
case 0:
break;
case 1:
{
NSArray *pendingAdNotifiacations=[[NSUserDefaults standardUserDefaults] objectForKey:#"pendingAdNotifiacations"];
NSMutableDictionary *addDictionary=[[NSMutableDictionary alloc] initWithDictionary:messageInfo copyItems:YES];
[addDictionary setObject:[NSNumber numberWithInteger:info] forKey:#"ad_id"];
if(!pendingAdNotifiacations){
pendingAdNotifiacations=[NSArray arrayWithObject:addDictionary];
}else{
pendingAdNotifiacations=[pendingAdNotifiacations arrayByAddingObject:addDictionary];
}
[addDictionary release];
[[NSUserDefaults standardUserDefaults] setObject:pendingAdNotifiacations forKey:#"pendingAdNotifiacations"];
[[NSUserDefaults standardUserDefaults] synchronize];
DLog(#"pendingAdNotifiacations received: %#.",pendingAdNotifiacations);
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:(pendingAdNotifiacations)?[pendingAdNotifiacations count]:0];
DLog(#"783- pendingAdNotifiacations: %lu.",(unsigned long)((pendingAdNotifiacations)?[pendingAdNotifiacations count]:0));
if(appInBackground){
[AdManager requestAndStoreAd:info];
}else{
[AdManager requestAndShowAd:info];
}
}
break;
default:
break;
}
}
This is the relevant code to download the info in the background using a background task:
-(void)requestAdinBackgroundMode:(NSInteger)adId{
DLog(#"744- requestAdinBackgroundMode begin");
if(_backgroundTask==UIBackgroundTaskInvalid){
DLog(#"744- requestAdinBackgroundMode begin dispatcher");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
DLog(#"744- passed dispatcher");
[self beginBackgroundUpdateTask];
NSURL *requestURL=[self requestURL:adId];
if(requestURL){
NSURLRequest *request = [NSURLRequest requestWithURL:requestURL];
NSURLResponse * response = nil;
NSError * error = nil;
DLog(#"744- NSURLConnection url: %#",requestURL);
NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
if(NSClassFromString(#"NSJSONSerialization"))
{
NSError *error = nil;
id responseObject = [NSJSONSerialization
JSONObjectWithData:responseData
options:0
error:&error];
if(error) {
NSLog(#"JSON reading error: %#.",[error localizedDescription]);
/* JSON was malformed, act appropriately here */ }
else{
if(responseObject && [responseObject isKindOfClass:[NSDictionary class]]){
if(responseObject && [[responseObject objectForKey:#"success"] integerValue]==1){
NSMutableDictionary *adDictionary=[[[NSMutableDictionary alloc] initWithDictionary:[responseObject objectForKey:#"ad"]] autorelease];
DLog(#"744- NSURLConnection everythig ok store: %#",adDictionary);
[self storeAd: adDictionary];
}
}
}
}
}
// Do something with the result
[self endBackgroundUpdateTask];
});
}
}
- (void) beginBackgroundUpdateTask
{
DLog(#"744- requestAdinBackgroundMode begin");
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void) endBackgroundUpdateTask
{
DLog(#"744- End background task");
[[UIApplication sharedApplication] endBackgroundTask: _backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}
Well this is all I think, I post it because someone asked me to post an update, I hope it may help someone...

Resources