Schedule notification in background task - ios

I'm developing a Calendar/Alarm app for iOS which is synchronising with a web server. When an activity is added on the server, a push notification is sent out so that the iOS client can fetch the new data and, if needed, update and schedule the time for next alarm (local notification).
But this only works when the app is open on client side. I would like the client to receive the push notifications and if needed, re-schedule the time for next alarm in background.
Is this impossible on iOS?

You can use Background Fetch for this, where the OS will "wake up" your app periodically to perform data fetching in the background.
First, enable the background fetch capability for your app. In XCode 6, view your project, then go to the Capabilities tab, turn on Background Modes, and check Background Fetch.
Then you'll have to implement some codes in the App Delegate:
In application:didFinishLaunchingWithOptions:, add:
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
The above sets how often you wish the system to "wake up" your app for background processes ideally. Note that the final frequency is determined by an algorithm in the iOS, so it may not always be this often.
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
//fetch code here
completionHandler(UIBackgroundFetchResultNewData);
}
The above is the actual overridden function that is called during this period of background process. Remember to call the completionHandler - failing to do so might reduce the chance of your app being run in the background next time (or so says the docs). The enums you may pass to the completionHandler are UIBackgroundFetchResultNewData, UIBackgroundFetchResultNoData, UIBackgroundFetchResultFailed. Use one of these depending on the result of your fetch.

// use this methods in Appdeleagte
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
[self showAlarm:notification.alertBody];
application.applicationIconBadgeNumber = 1;
application.applicationIconBadgeNumber = notification.applicationIconBadgeNumber-1;
}
// call this in appdelagete
-(void)makeNotificationRequest:(UILocalNotification *)notification1
{
[self showAlarm:notification1.alertBody];
}
// call this mathods in appdelagte
- (void)showAlarm:(NSString *)text {
**strong text**
// set notification and call this notification methods your another view .....
[[NSNotificationCenter defaultCenter] postNotificationName:#"uniqueNotificationName" object:self]; //leak
}

Related

Asynchronous code doesn't execute until app foregrounded in application:didReceiveRemoteNotification:fetchCompletionHandler:

In our app, we want to download a small amount of data in response to a push notification. So far, push notifications are working smoothly, launching the app in the background and causing didReceiveRemoteNotification to be called.
The problem is that, after this method returns, the app doesn't get any more CPU time until it's foregrounded again, so there's no opportunity to fetch that data asynchronously in the background.
Reducing this to the simplest case, I'm still unable to get asynchronous code running.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[application setApplicationIconBadgeNumber:1];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[application setApplicationIconBadgeNumber:9];
completionHandler(UIBackgroundFetchResultNewData);
});
}
In response to a push, the app launches in the background and the badge count is set to 1, but the badge number is not set to 9 until the app is launched from the home screen.
Shouldn't iOS keep running the app until that completion handler is called, up to 30 seconds?
The Info.plist has the remote-notification background mode specified, the push payload contains 'content-available' : '1', and I'm not quitting the app by swiping up in the app switcher.
To add, we're using Parse to send this push notification using the following Javascript:
Parse.Push.send({
where: installationQuery,
data: {
"content-available": 1,
}
}, { success: function() {},
error: function(error) {}
});
First take a here and make sure you enabled push notification and added the content-available field:
Using Push Notifications to Initiate a Download
If your server sends push notifications to a user’s device when new content is available for your app, you can ask the system to run your app in the background so that it can begin downloading the new content right away. The intent of this background mode is to minimize the amount of time that elapses between when a user sees a push notification and when your app is able to able to display the associated content. Apps are typically woken up at roughly the same time that the user sees the notification but that still gives you more time than you might have otherwise.
To support this background mode, enable the Remote notifications option from the Background modes section of the Capabilities tab in your Xcode project. (You can also enable this support by including the UIBackgroundModes key with the remote-notification value in your app’s Info.plist file.)
For a push notification to trigger a download operation, the notification’s payload must include the content-available key with its value set to 1. When that key is present, the system wakes the app in the background (or launches it into the background) and calls the app delegate’s application:didReceiveRemoteNotification:fetchCompletionHandler: method. Your implementation of that method should download the relevant content and integrate it into your app.
When downloading any content, it is recommended that you use the NSURLSession class to initiate and manage your downloads. For information about how to use this class to manage upload and download tasks, see URL Loading System Programming Guide.
Next, is there a reason your using "dispatch_after" with 2 seconds delay?
It is possible that since you call "dispacth_after" by the end of the run loop iOS "thinks" there's no pending work to do and puts the process to sleep so by the time the block is dispatched no one is listening to it.
Replacing it with "dispatch_async" might solve this.
Finally, if you do need to delay, you should tell iOS you need some time in the background, like this -
UIApplication *application = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier __block backgroundTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
if (backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
Then do your background work.
Don't forget end the task when your work is done. call something like -
if (backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}

Call a method in specific time even if app is killed

I am developing an app which only works in 8.30am to 5.30pm. I want to store the data only in between 8.30am to 5.30 pm. I used local notification for doing so. But it only works when user tap the notification.In 8.30am and 5.30pm, i need to execute some code even if the app is killed. Is there any other mechanism to do so...?
Here is my code:
UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (notification!=nil)
{
[self application:application didReceiveLocalNotification:notification];
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
code to be executed;
}
There is no way to execute a method in your app if the app is killed. You can bring the app up in the background using silent notifications. But silent notifications are better suited for News apps or apps which need to download content in the background so it is readily available for users when the app comes to the foreground.
Apart from this, the only way to execute the method is when it is either in the foreground or at least active in the background (using one of the available background modes). If your app is using a background mode only to stay in the background, Apple will reject the app, so be careful.

didReceiveRemoteNotification not being called when I tap on app icon after receiving a push notification while on background

When my app is on background and I receive a remote notification, two things can happen:
I tap on the push notification banner, my apps comes to foreground and didReceiveRemoteNotification is called.
I tap on my app icon from the springboard, my app comes to foreground and didReceiveRemoteNotification IS NOT called.
So, in the scenario 1, I can update my counter of unread messages inside the app in response to didReceiveRemoteNotification.
In the scenario 2, I can't.
How can I solve this using Quickblox?
As one possible variant:
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (userInfo) {
[self handleRemoteNotifications:userInfo];
}
// Override point for customization after application launch.
return YES;
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[self handleRemoteNotifications:userInfo];
}
#pragma mark - Remote notifications handling
-(void)handleRemoteNotifications:(NSDictionary *)userInfo {
// do your stuff
}
#end
When app is not running, in didFinishLaunchingWithOptions: you can use this code for get push's payload:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSDictionary* userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
NSString *myKey = [userInfo objectForKey:#"myKeyFromPayload"];
}
Remember to set permission in plist
For the remote push you can use in your appdelegate:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
The issue is probably that application:didReceiveRemoteNotification: is not called if the app is not running. To quote Apple documentation:
This document is outdated
If the app is not running when a push notification arrives, the method launches the app and provides the appropriate information in the launch options dictionary. The app does not call this method to handle that push notification. Instead, your implementation of the application:willFinishLaunchingWithOptions: or application:didFinishLaunchingWithOptions: method needs to get the push notification payload data and respond appropriately.
This is the new document
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.
You have to enable Remote Notifications in the Background Modes.
To do so, automatically: (Xcode5)
- Go to your Project settings -> Capabilities -> Background Modes
- Tick "Remote Notifications"
To do so, manually:
- Open your %appname%-Info.plist
- Right click and tick "Show Raw Keys/Values"
- Right click and choose "Add Row"
- Type in "UIBackgroundModes" (Key)
- The key will be created, and the type is an Array
- Add new item in the array with the value of "remote-notification" (Value) and press enter
- Now you have 1 item in your array called: "Item 0", if you had any other items in there, just add this item (remote-notification) to the array.
Be sure to use these methods frankWhite used :)
Hope this helps ;)

UIApplicationBackgroundRefreshStatusDidChangeNotification usage without corresponding delegate method

I feel that UIApplicationBackgroundRefreshStatusDidChangeNotification introduced in iOS 7 is of little use without supporting UIApplication delegate method. Because, the app is not notified when user has switch ON the background refresh state for my app.
This is my notification handler...
- (void)applicationDidChangeBackgroundRefreshStatus:(NSNotification *)notification
{
NSLog(#"applicationDidChangeBackgroundRefreshStatus with notification info = %# and refresh status = %d", notification, UIApplication.sharedApplication.backgroundRefreshStatus);
if (UIApplication.sharedApplication.backgroundRefreshStatus == UIBackgroundRefreshStatusAvailable) {
// if ([CLLocationManager locationServicesEnabled]) {
[self.locationManager startUpdatingLocation];
// }
}
}
As above, I want to start updating core location when UIBackgroundRegreshStatus is made Available through app Settings > General > Background App Refresh. I feel there should have been an appropriate delegate method in UIApplicationDelegate to let the app know about this change so that App could re-establish everything it needs to.
Either I'm missing something (pre-existing API) or Apple SDK engineers have some other/limited intentions about this notification usage. Please advice.
ideally, you never have to check for that setting. It looks as though you are going around background fetching the wrong way. With the application minimised, the system will periodically wake your application up and allow it to perform tasks. From your code , you want to update the location. first place to start is here , using this delegate method that gets called when the app is woken up for a background fetch
/// Applications with the "fetch" background mode may be given opportunities to fetch updated content in the background or when it is convenient for the system. This method will be called in these situations. You should call the fetchCompletionHandler as soon as you're finished performing that operation, so the system can accurately estimate its power and data cost.
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler NS_AVAILABLE_IOS(7_0);
this is how you use it, in your application delegate implementation, define the method body as follows
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(#"APP IS AWAKE FOR A BACKGROUND FETCH");
//do all the work you want to do
//once done, its important to call the completion hadler, otherwise the system will complain
completionHandler(UIBackgroundFetchResultNewData);
}
however, since you are updating the location, which has its own delegates, you only want the completion handler to be called when your delegates return and not before that. Calling the completion handler will send your application back to sleep. Since the completion handler is a block object, it can be passed around like any other object. One way of doing that is as follows: in the application delegate header file, define a block object:
void (^fetchCompletionHandler)(UIBackgroundFetchResult);
then in your performFetchWithCompletionHandler have :
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
fetchCompletionHandler = completionHandler;
NSLog(#"APP IS AWAKE FOR A BACKGROUND FETCH");
//do all the work you want to do
[self.locationManager startUpdatingLocation];
}
at some appropriate time, after your location delegate methods have returned, you call
fetchCompletionHandler (UIBackgroundFetchResultNewData);
be sure to check if your fetchCompletionHandler is non nill, calling it when nil will immediately crash your app. read more on blocks from the apple docs here https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
also have a look at the call [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval: ]; which Specifies the minimum amount of time that must elapse between background fetch operations.
You would probably chuck that into your app delegate application didFinishLaunchingWithOptions method.
hopefully this helps you.
You need to register for notification:
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationBackgroundRefreshStatusDidChangeNotification
object: [UIApplication sharedApplication]
queue:nil
usingBlock:^(NSNotification* notification){
NSLog(#"Just changed background refresh status because of this notification:%#",notification);
}];
You will get this output if your app is running and then you switch and disable background App refresh.
> Just changed background refresh status because of this notification:NSConcreteNotification 0x165657e0 {name = UIApplicationBackgroundRefreshStatusDidChangeNotification; object = <UIApplication: 0x16668670>}
You can obviously select a different queue. Also, this does not have to be in the app delegate. Any class can listens to this notification.
Recalling WWDC sessions, they said this was not available in DP seed 1. I am not sure if this is not an app delegate method now because they did not get to it or that is how they intended it since this is more likely to be used outside the app delegate (in your own model or VC) to listen and adjust to an app background refresh privileges being changed. In the latter case, a notification to anyone who registers for it makes more sense.
Hope this helps.
Update:
Based on the WWDC 2013 Core location video, Location updates that you want to work in the background has to start in the foreground. So this means that once the user turns off the background refresh for the app, location updates would stop and the only thing that would restart location updates, is restarting location updates in the foreground! In that case, a delegate would not be useful since it cannot restart location updates while in background. This is by design.

Newsstand background notification call aborts (time limit?)

I am developing a iOS Newsstand App.
I use the background download feature.
Everything except the following works fine:
If the App is in the background (started and then home button clicked) and it receives a remote push notification the method [UIApplicationDelegate didReceiveRemoteNotification] is called as expected. Then I do some calculations to get all the download URL's.
The first time the push is received this works fine.
During the handling of the following pushes the called syncData Method is aborted.
The whole computation can take up to 10 seconds.
Is there something like a time out on background calls?
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSLog(#"Received push notification: %#", userInfo.description);
[self.downloadManager syncData];
}
From what I understand, you need to call this method:
[UIApplication beginTaskWithExpirationHandler:];

Resources