Scheduled LocalNotification in iOS not seen in scheduledLocalNotifications straight away - ios

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.

Related

NSTimer Logic Failing Somewhere

I've been able to reproduce a defect in our app twice, but most times I fail. So I'm trying to understand what could possibly be going on here and hopefully have some new things to try. Our app times out and logs the user out after 10 minutes using an NSTimer. Every time the screen is touched the timer is reset, this all works great.
When the user backgrounds the app and comes back, the following code gets called:
- (BOOL)sessionShouldTimeOut {
if (self.timeoutManager) {
NSTimeInterval timeIntervalSinceNow = [self.timeoutManager.updateTimer.fireDate timeIntervalSinceDate:[NSDate date]];
if (timeIntervalSinceNow < 0) {
return YES;
} else {
return NO;
}
}
return NO;
}
- (void)timeoutIfSessionShouldTimeOut {
if ([self sessionShouldTimeOut]) {
[self.timeoutManager sendNotificationForTimeout];
}
}
This (I suspect) is the code that's failing. What happens when it fails is the user logs in, hits the home page and locks their phone. After 10+ minutes, they unlock and the app isn't logged out. When they come back, it's the code above that gets executed to log the user out, but in some scenarios it fails - leaving the user still on the homepage when they shouldn't be.
Here's my current theories I'm trying to test:
The timer somehow fires in the background, which then runs the logout routine, but since we're in the background the UI isn't updated but the timer is invalidated (we invalidate the timer after logout) I'm not sure if UI code called from the background will be shown after the app is in the foreground, so this may not be a possibility.
The user actually is coming back a few seconds before the timer fires, then after a few seconds when it should have fired it doesn't since it was backgrounded for 10 minutes. Do timers continue to hit their original fire time if the app goes to the background?
Somehow, while in the background, self.timeoutManager, updateTimer, or fireDate are being released and set to nil, causing the sessionShouldTimeOut method to return NO. Can variables be nilled in the background? What would cause them to if they could be?
The logout routine gets run while the phone is taking a while to actually move to the app, potentially causing the UI updates to not be reflected?
I'm very open to other theories, as you can see a lot of mine are very very edge case since I'm not sure at all what's happening.
I'd appreciate any guidance anyone can offer as to what else I may be able to try, or even any insights into the underworkings of NSTimer or NSRunLoop that may be helpful in this scenario (the documentation on those is terrible for the questions I have)
In AppDelegate.h set
applicationDidEnterBackground:
UIBackgroundTaskIdentifier locationUpdater =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:locationUpdater];
locationUpdater=UIBackgroundTaskInvalid;
} ];
This tells the os that you still have things going and not to stop it.

Receiving most recent old PubNub message after unsubscribing and resubscribing to channel

I just started using the PubNub iOS SDK v4.0 in my project.
The whole goal of using PubNub is a user will be subscribed to a certain channel based on what UIViewController they are currently on.
So the user should never be subscribed to or receiving messages from more than one channel at any given time.
Unfortunately I can't get this to work properly. Here's the example scenario that keeps happening to me:
I store and setup the client and configuration properties in the App Delegate like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Pubnub
self.configuration = [PNConfiguration configurationWithPublishKey:pubNubPublishKey
subscribeKey:pubNubSubscribeKey];
self.client = [PubNub clientWithConfiguration:self.configuration];
return YES;
}
User lands on View Controller A and I subscribe them to Channel_A:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// App Delegate
self.appDelegate = [[UIApplication sharedApplication] delegate];
// PubNub
[self.appDelegate.client addListener:self];
[self.appDelegate.client subscribeToChannels:#[#"Channel_A"] withPresence:NO];
}
User leaves View Controller A to go to View Controller B, so I unsubscribe them from Channel A:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Remove listener + unsubscribe
[self.appDelegate.client removeListener:self];
[self.appDelegate.client unsubscribeFromChannels:#[#"Channel_A"] withPresence:NO];
}
I then use the same code structure as above to subscribe the user to Channel_B once they segue to View Controller B.
I publish 2 new PubNub messages on View Controller B, one goes to Channel B and another one goes to Channel_A. The current user only receives the message for Channel_B, but any other users on the app currently on View Controller A will receive the Channel_A message.
This almost works perfectly. The current user on View Controller B only receives the Channel_B message, but here's where I run into problems.
When the user leaves View Controller B and goes back to View Controller A, they instantly receive the most recent message that was just posted moments ago from View Controller B for Channel_A.
I don't understand why they are receiving this message when they were unsubscribed from Channel_A while on View Controller B.
I have even tested it and waited a minute to pop back to View Controller A, and I still always receive the most recent Channel_A message that was posted a minute ago.
I don't want this to happen. They should only receive real time messages for that channel, not one that happened 10 seconds ago, 30 seconds ago, etc. when they were unsubscribed from that channel.
I did some quick research and thought setting the following properties in the App Delegate might help, but I still experience this problem:
self.configuration.restoreSubscription = NO;
self.configuration.catchUpOnSubscriptionRestore = NO;
Just figured it out. I had to add the following line of code for my configuration:
self.configuration.keepTimeTokenOnListChange = NO;
By default this is set to YES and will retrieve the most recent message when subscribing to a channel.
I also was able to stop setting catchUpOnSubscriptionRestore to NO but still had to keep setting restoreSubscription to NO.

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.

NSNotificationCenter callback while app in background

One question and one issue:
I have the following code:
- (void) registerForLocalCalendarChanges
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(localCalendarStoreChanged) name:EKEventStoreChangedNotification object:store ];
}
- (void) localCalendarStoreChanged
{
// This gets call when an event in store changes
// you have to go through the calendar to look for changes
[self getCalendarEvents];
}
These methods are in a class/object called CalendarEventReporter which contains the method getCalendarEvents (in the callback).
Two things:
1) If the app is in the background the callback does not run. Is there a way to make it do that?
2) When I bring the app back into the foreground (after having changed the calendar on the device) the app crashes without any error message in the debug window or on the device. My guess is that the CalendarEventReporter object that contains the callback is being garbage-collected. Is that possible? Any other thoughts on what might be causing the crash? Or how to see any error messages?
1) In order for the app to run in the background you should be using one of the modes mentioned in the "Background Execution and Multitasking section here:
uses location services
records or plays audio
provides VOIP
services
background refresh
connection to external devices
like through BLE
If you are not using any of the above, it is not possible to get asynchronous events in the background.
2) In order to see the crash logs/call stack place an exception breakpoint or look into the "Device Logs" section here: Window->Organizer->Devices->"Device Name" on left->Device Logs on Xcode.
To answer your first question, take a look at https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
What I did to get code running in the background is to do something like
In the .h file
UIBackgroundTaskIdentifier backgroundUploadTask;
In the .m file
-(void) functionYouWantToRunInTheBackground
{
self.backgroundUploadTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
//code to do something
}
-(void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUploadTask];
self.backgroundUploadTask = UIBackgroundTaskInvalid;
}
The code above I pretty much learned from objective c - Proper use of beginBackgroundTaskWithExpirationHandler
As for your second question, you should set a breakpoint where code is supposed to run when you bring the app back to the foreground. No one can figure out why an app crashes if not given enough code or information.
The solution to the second part of the question was to raise the scope of the object containing the callback code. I raised it to the level of the containing ViewController. This seems to work. I still can't figure out how to raise the Notification (i.e. execute the call back) if the notification comes while the app is in the background/suspended. This prevented the object containing the callback from being cleaned up.

Good pattern for UILocalNotifications and applicationIconBadgeNumber

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

Resources