Good pattern for UILocalNotifications and applicationIconBadgeNumber - ios

My app schedules UILocalNotifications to be delivered to its users at varying times of the user's choice.
I'm running into a situation with how to manage the applicationIconBadgeNumber in this scenario.
As we know, you have to set the badge number at the time you create the notification. My problem is that the state of the number of badges can change at any time. Consider this scenario:
1) User gets 3 notifications.
2) User creates a new notification to alert her at a given point of time in the future. This notification carries the value of 1 plus the current value of the application badge (3).
3) User goes about their business. In the process of their business, they clear all 3 notifications (and, thus, badge numbers) they currently have by viewing them or otherwise using the app.
4) After the given amount of time passes, the notification appears in iOS, along with its previously calculated value (4, if you don't remember).
5) The application badge is now, 4 even though the user only has one actual notification.
I have searched up and down, but I cannot find an answer to this question which almost certainly has a simple answer I'm completely missing. How do I solve this quandary?

Since your app cannot look in the future, and know which events you'll handle immediately, and which ones you'll leave 'pending' for a while, there's some trick to do :
When notifications are handled by your app (by tapping on the notification(s), icon, ...), you have to :
get a copy of all pending notifications
'renumber' the badge number of these pending notifications
delete all pending notifications
re-register the copy of the notifications with their corrected badge
numbers again
Also, when your app registers a new notification, it has to check how many notifications are pending first, and register the new notification with with :
badgeNbr = nbrOfPendingNotifications + 1;
Looking at my code, it will get clearer. I tested this, and it's definitely working :
In your 'registerLocalNotification' method you should do this :
NSUInteger nextBadgeNumber = [[[UIApplication sharedApplication] scheduledLocalNotifications] count] + 1;
localNotification.applicationIconBadgeNumber = nextBadgeNumber;
When you handle the notification (appDelegate), you should call the method below, which clears the badge on the icon and renumbers the badges for pending notifications (if there are any)
Note that the next code works fine for 'sequential' registered events. If you would 'add' events in between pending ones, you'll have to 're-sort' these events first. I didn't go that far, but I think it's possible.
- (void)renumberBadgesOfPendingNotifications
{
// clear the badge on the icon
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
// first get a copy of all pending notifications (unfortunately you cannot 'modify' a pending notification)
NSArray *pendingNotifications = [[UIApplication sharedApplication] scheduledLocalNotifications];
// if there are any pending notifications -> adjust their badge number
if (pendingNotifications.count != 0)
{
// clear all pending notifications
[[UIApplication sharedApplication] cancelAllLocalNotifications];
// the for loop will 'restore' the pending notifications, but with corrected badge numbers
// note : a more advanced method could 'sort' the notifications first !!!
NSUInteger badgeNbr = 1;
for (UILocalNotification *notification in pendingNotifications)
{
// modify the badgeNumber
notification.applicationIconBadgeNumber = badgeNbr++;
// schedule 'again'
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
}
}
Credits to #Whassaahh

Related

APNS Background handling for badge counter

I am integrated APNS in my app, The requirement is to maintain notification count when app in background. For example we got notification in background in there is key counter count , i,e changing dynamic in every notification, Can it possible to handle in iOS when app is background or app has forcefully closed.
This is the APNS payload from back end server.
{
"aps" : {
"alert" : "You got your emails.",
"badge" : 9,
"sound" : "bingbong.aiff"
},
"acme1" : "bar",
"acme2" : 42
}
The value for key badge is automatically considered as badge count.On ios app side no need to calculate or handle the count.
In above example 9 is the badge count.So your app icon will show 9.
NOTE While your app is close u can't handle badges on your own.Thats why we are using badge key from APNS Payload
For better clarification about notification see documentation
EDIT: if you want to reduce the badge count on your own.Decrement the count and update it yourself.as follows
NSInteger numberOfBadges = [UIApplication sharedApplication].applicationIconBadgeNumber
numberOfBadges -=1;
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:numberOfBadges];
or else make the count to 0, so that badge icon will be disappeared.Add the below code in ** applicationDidBecomeActive**
application.applicationIconBadgeNumber = 0;
For managing the notification counter you have to setup functionality at server. lets take Facebook example if you are not reading the new notification the counter keep increase by one and once you click on notification it comes back to zero. so whenever you read new notification it also managed on server side whether is user opened it or not.
lets say there are 3 notifications user di't see than for next one counter will be 4.
And once your receive the count from server with update, handle it like if all updates are viewed application badge counter set to 0
this is my understanding as i have implemented the same in my iOS application if anyone has any good solution. please suggest.
I hope this picture(source google) will give the complete understanding.

How to Reset Local Notifications when App Re-opens

I am developing a reminder app with custom Repeat Intervals for local notifications. What I basically want is the DueDate notification to fire then subsequent notifications fire afterwards.
So Notification1.fireDate = the DueDate.date
Notification2.fireDate = DueDate.date.dateByAdding(1000)
& continue for 10 or so times. Then when the app becomes active again, I want the line of notifications to loop again but start after the last notification, like a Queue.
This is all to create the illusion of custom repeat intervals. This is where I got the idea from, so it can be done https://stackoverflow.com/a/5765870/5601784
If you are not changing the fireDate, then there is no point in deleting the previously set notification and setting it again. However, to delete all the notification that your app has set you can do this.
UIApplication.shared.cancelAllLocalNotifications()
or
let app: UIApplication = UIApplication.shared
for event in app.scheduledLocalNotifications! {
let notification = event as UILocalNotification
app.cancelLocalNotification(notification)
}
print(app.scheduledLocalNotifications!.count))//prints -- 0
Then you can set the UILocalNotification with the new information.

Update iOS icon badge number

I have a icon badge number update requirement. The app tracks tasks. I want the app to have a badge displaying the number of tasks due on each day. There are basically two cases when the badge number needs to be updated:
Midnight every day.
If new tasks are added or tasks are removed.
I know how to handle the second case. I can set badge number in the applicationResignActive func. However, the midnight automatic update is trick for me. To update the badge number, I need to call a func of the app to count the tasks that due on the day. However, in midnight, the app may be in all possible situations: foreground, background and not running. How can I do this? Thank you.
=====================================
To be clearer with my requirement, I would like the badge number to be updated everyday correctly, even the user never opens the app for a whole day or for consecutive several days. Also, I would try to avoid server side support because the app is a standalone app so far. Much appreciated for any help.
=====================================
Final update: I accepted Vitaliy's answer. However, his answer requires the app to be opened at least once every day. Otherwise, the event won't fire and the badge number cannot be updated.
Also, in my case, every time the app enters background event fires, I have to remove the existing notification and schedule a new one, with the up-to-dated badge number recalculated.
I am still interested in some way to handle the case that the app is not opened every day, how can you make sure the badge number is correct. So far, the easiest way is to setup some server and let it push notifications to the app regularly.
You can achieve it with UILocalNotification:
When app goes to background, calculate exact badge count number for nearest midnight
Schedule UILocalNotification at the nearest midnight with your calculated badge count
You will get notification at midnight, and app's badge count will be updated
Example code:
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Calculate nearest midnight or any other date, which you need
NSDate *nearestMidnight = [self nearestMidnight];
// Create and setup local notification
UILocalNotification *notification = [UILocalNotification new];
notification.alertTitle = #"Some title";
notification.alertBody = #"Some message";
notification.fireDate = nearestMidnight;
// Optional set repeat interval, if user didn't launch the app after nearest midnight
notification.repeatInterval = NSCalendarUnitDay;
// Calculate badge count and set it to notification
notification.applicationIconBadgeNumber = [self calculateBadgeCountForDate:nearestMidnight];
[application scheduleLocalNotification:notification];
}

Scheduled LocalNotification in iOS not seen in scheduledLocalNotifications straight away

The problem I am encountering is that I successfully schedule a local notification for between 1 and 2 hours from now, I then go back to my main root view controller where some code runs and displays a different box depending on whether a local notification is scheduled or not. So the code is:
/// Commit notification
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
[self performSegueWithIdentifier:#"exitToStart" sender:self];
And then on the main root controller a method is called straight away on return to refresh the view that contains:
NSArray *currentNotifications = [[UIApplication sharedApplication] scheduledLocalNotifications];
if ([currentNotifications count] > 0)
{
...
}
else { ... }
The problem I am having is that sometimes currentNotifications count is 0 and sometimes it is 1 and so the wrong box is displayed. I think what is happening is sometimes iOS is still scheduling the notification and sometimes it is not, assuming it does it in the background without blocking the thread?
I know the local notification always gets there because if I come in and out of root when the problem occurs then it always finds the local notification.
Is there a way I can wait for it to be scheduled before looking for notifications? That complies with Apple's guidelines. Within the if block I go on to use info from the notification.
I couldn't see a way to guarantee that by the time I was back in main controller, the notification would always be scheduled. So to get around this I pass back the information I put into the local notification myself to the main controller in its return method. Later returns to the main controller from other areas then have given the local notification the 1 or 2 seconds it needs to schedule itself and can use the existing logic.

cancelLocalNotification doesn't seem to work

In the following code I check whether the current task (the one being edited) has a localNotification. If so, I try to cancel that notification. Then (if the fireDate hasn't already passed) I update the alertBody of the notification with the (possibly) changed information and re-schedule it.
The problem is that if I create a notification and then edit it, I end up with two notifications going off. Somehow the original one isn't getting canceled...
if ([currentTask localNotification])
{
[[UIApplication sharedApplication] cancelLocalNotification:[currentTask localNotification]];
if ([[NSDate date] compare:[currentTask localNotification].fireDate] == NSOrderedAscending)
{
NSLog(#"current task name: %#", [currentTask name]);
NSString *temp = [NSString stringWithFormat:#"Alert:\n%#", [currentTask name]];
[[currentTask localNotification] setAlertBody:temp];
[[UIApplication sharedApplication] scheduleLocalNotification:[currentTask localNotification]];
}
else
[currentTask setLocalNotification:nil];
}
Any insights as to the problem here?
I know that a copy is made of the notification when it's scheduled - how does cancelLocalNotification find the correct copy? Does it do it by testing the properties for equality? Or does it make a copy of the pointer address and match that up? In other words, if one of the properties is changed, will cancelLocalNotification not be able to match it with the notification that was originally scheduled?
I'll add my findings here for iOS 9. Previously I had code that went like so:
UILocalNotification* notification = getNotification();
[[UIApplication sharedApplication] cancelLocalNotification: notification];
notification.alertBody = newBody;
[[UIApplication sharedApplication] scheduleLocalNotification: notification];
Worked great before iOS 9, cancelling the existing instance and then rescheduling it with a new alertBody or soundName. Then along comes iOS 9 and suddenly running this code doesn't actually cancel the previous notification and so I was ending up with more notifications than I wanted!
The fix for me was to not be lazy in reusing the old notification so I now create a new notification and populate it with the fields of the old notification. Works a treat!
EDIT: I still seem to be getting problems with [[UIApplication sharedApplication] cancelLocalNotification: notification] not working in other situations... I've got my notifications automatically repeating themselves every minute and it seems there might be a bug in Xcode 7.0 meaning its sometimes ignored: https://forums.developer.apple.com/thread/9226
EDIT II: Looks like repeating local notifications scheduled in the app before it was built against iOS 9 can't be cancelled.. at least not easily... still working on finding a solution to this as my app is centered around repeating local notifications for reminders!
EDIT III: I've managed to get rid of a lingering, un-cancellable notification by force quitting the app after an attempt to cancel it... and that seems to have worked... it's not a great solution, certainly not something I want to get my users to do if there's some kind of alternative... I've posted a separate question to see if anyone has any further ideas.
I came across the same issue. the solution i used was.
// Sort the notification on the fire date of the notification.
NSSortDescriptor * fireDateDesc = [NSSortDescriptor sortDescriptorWithKey:#"fireDate" ascending:YES];
//get all scheduled notifications.
NSArray * notifications = [[[UIApplication sharedApplication] scheduledLocalNotifications] sortedArrayUsingDescriptors:#[fireDateDesc]];
// Cancel all scheduled notifications.
[[UIApplication sharedApplication] cancelAllLocalNotifications];
for (int i=0; i<[notifications count]; i++){
UILocalNotification* oneEvent = [notifications objectAtIndex:i];
// update notification data here an then schedule the notification.
[[UIApplication sharedApplication] scheduleLocalNotification:oneEvent];
}
Canceling UILocalNotifications one by one to update something in the notification produces an issue that sometimes the notification does not get cancelled.
So the solution i have implemented is:
1: get all scheduled notifications and store them in whatever you like.
2: Then cancel all scheduled notifications.
3: From step one loop through the notifications, update whatever you want and schedule notifications on the go.
hope this helps.
It looks like if any of the properties of a UILocalNotification have been changed since it was scheduled, that cancelLocalNotification won't work. That makes me think that cancelLocalNotification must check equality of the different properties against the currently scheduled copies of notifications to see which one is a match.

Resources