Background App Refresh is a new feature added to iOS 7. What it does is allows apps to quietly wake up and download content all while in the background.
Some of the built in Apple apps such as Mail, Contacts and the Calendar have always done this. When a new email arrives, you open the app and the email is there. Likewise, you add a contact on a remote device and it is automatically added to your contact list on your iPhone. On the other hand, all 3rd party apps could not do this. When notifications of a new email arrive, you open the app and then need to wait for syncronisation to happen. The same happens with apps like Evernote where your new notes are not downloaded until you open the app and sync it.
Background App Refresh changes this. iOS 7 developers can now add a few changes to their code and when done, iOS creates a schedule based on how often you use the app and then at intervals through the day, the app will quietly fetch data from your server so that when the user opens up the app, the syncronisation is already done.
Many apps have already taken advantage of this feature. This includes Dropbox, Evernote, Instapaper, Mailbox, Newsify and OmniFocus to name just a few.
Today, I want to show you how you can get your app to take advantage of this iOS 7 feature.
Background App Refresh Tutorial
The Background App Refresh service is part of the UIApplicationDelegate protocol which is declared by the AppDelegate (UIApplication) class. There are several properties and delegate methods which you need to be aware of to get this working. You also need to add an entry in your plist which I will cover shortly. One thing to take note of is that you get 30 seconds of time to complete the request for data before the app closes back down. If this causes problems (perhaps you are downloading a lot of data each time) then you’ll need to check the timer (backgroundTimeRemaining property) and act accordingly when the time is about to run out.
We have the following to work with:
UIApplication
Properties
applicationState
backgroundTimeRemaining
backgroundRefreshStatus
Instance Methods
setMinimumBackgroundFetchInterval:
beginBackgroundTaskWithName:expirationHandler:
beginBackgroundTaskWithExpirationHandler:
endBackgroundTask:
setKeepAliveTimeout:handler:
clearKeepAliveTimeout
UIApplicationDelegate
Delegate Methods
application:performFetchWithCompletionHandler:
application:handleEventsForBackgroundURLSession:completionHandler:
As we work through this quick tutorial, we’ll take a look at a few of the methods and properties above. You might need to use less/more or different methods than we use in the tutorial, but hopefully we can give you enough information and a basic framework to work with for the rest to click in place for your own app.
The sample app I will create today will get the current weather for your last known location at various times through the day. To do this, we’ll use a JSON feed from WorldWeatherOnline and pass it the current location (or last known location) and the app will pull down the feed and be ready to show current temperature when the app is launched. Beware that there are limitations with this. Background App Refresh works by observing how often you use an app and how often data is available. If you only launch the app once a week, it will check far less frequently than if you open your app at several times through the day. So running this test app might not immediately show some good results, but if you were to use this app more, the frequency at which the background app refresh occurs will be increased.
Lets begin with the usual way of setting up a Single View Application as described in this tutorial.
Next we need to allow Background Fetch. We can do this in two ways. The first is to add Required Background Modes to your info.plist file and then add “fetch” as a string in to that array. The easier way is to go to Capabilities for your app, toggle the Background Modes switch to On and then check the “Background Fetch” box. This automatically updates the info.plist file for you.
Now that the app is ready to work with Background Fetch, we can now start implementing what we need to get it working. Lets start with the storyboard and create a simple app which has a few UILabels and a button.
Here we have 7 UILabels with 3 of them being used to show information. We also have a UIButton that we put Refresh on to. CTRL+drag the UIButton to the ViewController.m file and give it a name (we called the method refreshTemp.
You next need to CRTL+Drag each of the 3 UILabels to the header and give them a name. I used currentLocation, currentTemp and lastRetreived. The code looks like the following:
Header
@property (weak, nonatomic) IBOutlet UILabel *currentLocation;
@property (weak, nonatomic) IBOutlet UILabel *currentTemp;
@property (weak, nonatomic) IBOutlet UILabel *lastRetreived;
Implementation
- (IBAction)refreshTemp:(UIButton *)sender {
}
Collecting the Weather Info
Now we move on to getting the current weather. The WorldWeatherOnline API works with JSON data. iOS has the ability to work with JSON. I wont cover this in too much detail but will provide the code and general instructions on how to work with the data provided. When we do that, we’ll be then adding some Core Location framework information and will then finally move on to pulling the information from the weather API by specifying where the phone is and then retrieving the weather locally.
Getting an API key
To work with WorldWeatherOnline you need to get an API key. Create an account with them (its free). Check your email and click the link within and then register your app. I just called mine Test Weather App and unchecked the premium trial box. After moving on, you then are given an API key. Save this key somewhere as it will be needed later.
We next need to format the JSON request. In the local weather on the API Explorer page I pasted in some coordinates for parameter q, set the num_of_days to 1 and the includelocation to yes. It provided this URL.
http://api.worldweatheronline.com/free/v1/weather.ashx?q=53.796815%2C-1.753237&format=json&num_of_days=1&includelocation=yes&key=YOUR_API_KEY
If you copy and paste your API key after the k= on the URL above and then paste it in to a browser, you should see some data returned. It is this data that we will work with although not here, but in the app when we make the JSON request.
Making the JSON Request
Lets now create a class of NSObject type. Call it GetWeather.
To do this, CMD+N and then select Objective-C class in the Cocoa Touch section of iOS. When created, load up the header and add the following method and properties.
@property (strong, nonatomic) NSString *currentLocation;
@property (strong, nonatomic) NSString *currentTemperature;
- (void)getWeatherAtCurrentLocation:(CLLocationCoordinate2D)coordinate;
We are going to use this class to handle requesting and getting the current weather for a specified coordinate. As we are using CLLocationCoordinate2D as an argument, we’ll need to import the CoreLocation Framework in to the header also.
#import
We now need to implement this method. Copy and paste the method name in and add curly brackets after it as follows:
- (void)getWeatherAtCurrentLocation:(CLLocationCoordinate2D)coordinate {
}
Lets go ahead and prepare the URL and get the data response.
Just below the @implementation line in the implementation file, I added the following:
static const NSString *WORLD_WEATHER_ONLINE_API_KEY = @"MY API KEY";
Please replace the MY API KEY string with your API key.
We then add the following to the getWeatherAtCurrentLocation method:
- (void)getWeatherAtCurrentLocation:(CLLocationCoordinate2D)coordinate {
NSString *urlString = [NSString stringWithFormat:@"http://api.worldweatheronline.com/free/v1/weather.ashx?q=%f,%f&format=json&num_of_days=1&includelocation=yes&key=%@", coordinate.latitude, coordinate.longitude, WORLD_WEATHER_ONLINE_API_KEY];
NSURL *weatherURL = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:weatherURL];
[self parseJSONData:data];
}
This can be cut down to just a single line by embedding/nesting each part of the request in to the single line, but to make it more clearer I thought it would be best to put it on several lines.
On line 2 we create an NSString and call it urlString. We then use the stringWithFormat class method from NSString to create a urlString. To do that, we paste in the URL provided by worldweatheronline.com and replace the coordinates with a %@ and also replace the %2C with a comma. We also replace the API with a %@. After that, we add in the coordinate.latitude and coordinate.longitude which replaces the first and second %@’s. The last %@ is replaced with the const we created earlier. Here we just put WORLD_WEATHER_ONLINE_API_KEY.
If you NSLogged the URL, you should then see a correctly formatted URL that could be pasted in to a browser to provide a result.
On the next line we convert that NSString in to an NSURL with the URLWithString class method.
We then create an NSData object on the next line and use the dataWithContentsOfURL class method and pass it the NSURL. This retrieves the information from the web request which we then pass on to a method I created called parseJSONData. This method is listed below:
- (void)parseJSONData:(NSData *)data {
NSLog(@"%@", data);
}
When you NSLog the data you wont see much, just a bunch of numbers showing data stored in NSData. We now need to parse this data in to something useable.
- (void)parseJSONData:(NSData *)data {
NSError *error;
NSDictionary *parsedJSONData = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
NSDictionary *weather = [parsedJSONData objectForKey:@"data"];
// Nearest Area and setting currentLocation property.
NSDictionary *nearestArea = [weather objectForKey:@"nearest_area"];
NSArray *array = [nearestArea valueForKey:@"areaName"];
NSDictionary *currentLocation = [array objectAtIndex:0];
NSArray *currentLocationString = [currentLocation valueForKey:@"value"];
self.currentLocation = currentLocationString.lastObject;
// Getting current temp in degrees C
NSArray *currentCondition = [weather objectForKey:@"current_condition"];
NSDictionary *tempDictionary = [currentCondition objectAtIndex:0];
NSArray *tempInC = [tempDictionary valueForKey:@"temp_C"];
//NSArray *weatherInC = [currentCondition objectForKey:@"temp_c"];
NSString *temperature = [NSString stringWithFormat:@"%@", tempInC];
self.currentTemperature = [temperature stringByAppendingString:@"C"];
}
Line 3 we use NSJSONSerialization to get the data and put it in an NSDictionary called parsedJSONData.
Line 6 we parse the data from the data key and store that in an NSDictionary.
Lines 9 – 13 we carry on digging in to the data until we get the current location and store that in the currentLocation property (this is the name of the place such as London).
In the next few lines we get the temperature in degrees in the same way. We keep digging in to the JSON file, storing parts in a dictionary and then dig deeper until we can get the NSString.
I haven’t gone in to much detail in this section as the main focus of the Background App Refresh tutorial is to show you how to pull data while the app is in the background. I just opted to choose weather although you can substitute this for IMAP or any other JSON or other kind of feed.
After we complete this section we have both properties of GetWeather set with the correct details for the location that will be fed to it.
Using the GetWeather Custom Class
At this point, GetWeather is a fully functioning class although nothing is actually putting it to use and hence, your app will not do anything if you run it in its current form. Lets remedy that by implementing code that can pass some coordinates to GetWeather and then update the view with what it finds in the properties.
In the ViewController header we have imported the Core Location framework and have set up 3 properties that are UILabels connected up to the view.
In the implementation of this class we just have a single method called refreshTemp: which is an IBAction and connected up to a UIButton on the view.
Lets start adding some code.
The header is actually complete at this point. The remaining part of this app will all be completed in the implementation, so switch to that if you are not already viewing it.
Just below the import line, we need to add the following lines of code:
#import "GetWeather.h"
@interface ViewController ()
@property (strong, nonatomic) CLLocationManager *locationManager;
@end
Just after the @implementation add the following:
BOOL weatherCalled = 0;
So, in the above we import GetWeather.h. We also create a property for CLLocationManager and call it locationManager.
We also add a BOOL that we call weatherCalled and initialise it with a 0. This will be used for making sure that multiple requests to the API are not made repeatedly.
We now need to add the following to the viewDidLoad method:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
Notice that we set the delegate to self. This means we need to make sure that we specify that this class conforms to the CLLocationManagerDelegate. Add this in the header (I said we were done there… oops) as follows:
@interface ViewController : UIViewController
What we did in the above 3 lines is alloc/init the locationManager and told it to startUpdatingLocation. We now need to implement a delegate method:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[self.locationManager stopUpdatingLocation];
CLLocation *location = [locations lastObject];
[self getWeather:location];
weatherCalled = 1;
}
This method is called each time new location information is available. When we have received the first update we call the stopUpdatingLocation method. We don’t need to continuously monitor that. Next, we get the lastObject of locations and store that in a CLLocation. We then pass that to a custom method called getWeather and then we set weatherCalled = 1. The reason for this is that although we shut down location updates, we sometimes still get a couple of requests made afterwards as it takes a bit of time to close down. We will use weatherCalled to make sure we don’t recall the API if a call was only just made.
Next we will implement the getWeather method. This is done as follows:
- (void)getWeather:(CLLocation *)location {
if (weatherCalled == 0) {
CLLocationCoordinate2D currentLocation = location.coordinate;
NSLog(@"%f, %f", currentLocation.latitude, currentLocation.longitude);
GetWeather *weather = [[GetWeather alloc] init];
[weather getWeatherAtCurrentLocation:currentLocation];
self.currentLocation.text = weather.currentLocation;
self.currentTemp.text = weather.currentTemperature;
NSDate *today = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterNoStyle;
formatter.timeStyle = NSDateFormatterShortStyle;
self.lastRetreived.text = [formatter stringFromDate:today];
weatherCalled = 0;
} else {
NSLog(@"Weather Check Already in Progress");
}
}
Line 1 we are asking for a CLLocation as an argument. We got this from the delegate method and passed this information over when we made the call.
Next we get a CLLocationCoordinate2D from that location.
We then alloc/init the GetWeather custom object.
We then call a method getWeatherAtCurrentLocation and pass it our current location.
We then update our labels with the properties of weather (currentTemperature and currentLocation). The reason we can do it this way is that the code to check weather is not run in a block and therefore, when the getWeatherAtCurrentLocation method finishes, the properties will be ready in time to be used to set the label text. Note: This isn’t good practice as it will hold up the main thread, but as this isn’t the main point of the tutorial, we’ll let it slide this time :) Just be prepared for the app to pause for a few seconds while the weather is updated.
After that, we want to get the current time. We do that we NSDate and NSDateFormatter. We ask or the current date and set the UILabel text property to be the current time on the device. We then set weatherCalled = 0 at this point.
Notice how this is in an if/else control statement. If we put in 2 requests for weather at the same time then it will just ignore the second request.
Running the app on a device or in the simulator will now work. Just a few pointers. First is that we haven’t got any error checking on location services. If you run in the simulator and have location disabled then the app will crash. Likewise, if you deny access to location services when prompted, it will also crash. But, if you are allowing locations and are specifying a test location through the debug menu, you will pull weather information where available.
One final update which activates the refresh button is to add the following code which switches back on the location updates:
- (IBAction)refreshTemp:(UIButton *)sender {
weatherCalled = 0;
[self.locationManager startUpdatingLocation];
}
Getting the Weather to Update in the Background
Now that we have a “functioning” app that parses data from the internet, lets look at how we can put that to use in the background.
What I will do in this tutorial is set up the background app refresh in the AppDelegate. I will then pull in the last known location, pass that to GetWeather and when the weather is retrieved, a notification, alarm and vibration will happen and a message will pop up telling you what the temperature is. I will also use the code at this point to set up a badge count showing the retrieved temperature in degrees C.
In ViewController.m we need to add the following (just under the BOOL weatherCalled = 0; line.
NSUserDefaults *standardDefaults;
We are going to use NSUserDefaults to store the last retrieved location.
At the end of the didUpdateLocations delegate method in ViewController.m, add this following code:
standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults setFloat:location.coordinate.latitude forKey:@"locationLatitude"];
[standardDefaults setFloat:location.coordinate.longitude forKey:@"locationLongitude"];
This sets up the standardDefaults and adds both a lat and lng coordinate to it. In the AppDelegate, we will pull this information out to provide is as the current location for GetWeather.
In AppDelegate.h, import the Core Location Framework.
#import
Add the following delegate method to the implementation:
application:performFetchWithCompletionHandler:
Now lets add this code in to AppDelegate.m
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"Fetch started");
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
// Get Current Location from NSUserDefaults
CLLocationCoordinate2D currentLocation;
currentLocation.latitude = [standardDefaults floatForKey:@"locationLatitude"];
currentLocation.longitude = [standardDefaults floatForKey:@"locationLongitude"];
// GetWeather for current location
GetWeather *getWeather = [[GetWeather alloc] init];
[getWeather getWeatherAtCurrentLocation:currentLocation];
// Set up Local Notifications
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
NSDate *now = [NSDate date];
localNotification.fireDate = now;
localNotification.alertBody = [NSString stringWithFormat:@"Temperature in %@ is %@.", getWeather.currentLocation, getWeather.currentTemperature];
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.applicationIconBadgeNumber = [getWeather.currentTemperature intValue];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
completionHandler(UIBackgroundFetchResultNewData);
NSLog(@"Fetch completed");
}
Now that we have implemented this method, each time iOS decides to do a background app refresh for this app, this method will be called.
Line 3 we are accessing the NSUserDefaults again.
Lines 6 – 8 we are setting up a CLLocationCoordinate2D and pulling the stored location from the NSUserDefaults.
After that, we alloc/init GetWeather and then call the getWeatherAtCurrentLocation and provide it the currentLocation that we just got from NSUserDefaults. A better way of doing this would be to set up the CLLocationManager as a singleton and then request the actual current coordinates. If your location is set to Bradford but you are now in London, you are likely to be more interested in the weather where you are rather than where you were. Take this as an assignment and see if you can make it work like that.
Next we set up the local notifications and specify the current date (meaning send the notification right now). In the alertBody property we provide an NSString stringWithFormat class method and insert the %@ to put the current location and current temperature.
We then specify a sound with soundName and following that, we specify the applicationIconBadgeNumber and pull the intValue from the currentTemperature property of GetWeather. That will drop the C off the end and provide the needed INT value.
Finally we schedule the localNotification which fires up the alert when the app is in the background.
Next we provide the UIBackgroundFetchResultNewData as the completionHandler although this is a sloppy way of integrating this. You should use if/then or switch (or some other way) to show the appropriate result.
One last step is that we need to set the interval, or I should say “suggest” the interval at which iOS will run the background app refresh. We do this with the following method in the AppDelegate.m file:
-(BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return true;
}
Here we are specifying UIApplicationBackgroundFetchIntervalMinimum as the recommended time. This lets iOS figure out based on your usage patterns.
Testing the Cheap and Cheerful Background Weather App
You can go ahead and test the app now. Due to little to no error checking, there are a few pre-requisites.
You need a device running iOS 7.
You need to ensure that when you load your app, you accept the message prompting about current location.
You need to ensure that you let your app run long enough to put the current location and temperature on the screen.
To simulate on a device, go to Xcode (when the app is running in the simulator) and click Debug > Simulate Background Refresh. The app will close out at this point and you will get an alert and the badge count will be updated to the current temperature.
If you are running in the simulator, make sure you are allowing it to simulate a location. In the iOS Simulator menu, click Debug > Location > Apple.
The full project can be downloaded here.
The full and final code is below. Please make sure you paste your API key in GetWeather.m.
ViewController.h
#import
#import
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *currentLocation;
@property (weak, nonatomic) IBOutlet UILabel *currentTemp;
@property (weak, nonatomic) IBOutlet UILabel *lastRetreived;
@end
ViewController.m
#import "ViewController.h"
#import "GetWeather.h"
@interface ViewController ()
@property (strong, nonatomic) CLLocationManager *locationManager;
@end
@implementation ViewController
BOOL weatherCalled = 0;
NSUserDefaults *standardDefaults;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
}
- (IBAction)refreshTemp:(UIButton *)sender {
weatherCalled = 0;
[self.locationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[self.locationManager stopUpdatingLocation];
CLLocation *location = [locations lastObject];
[self getWeather:location];
standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults setFloat:location.coordinate.latitude forKey:@"locationLatitude"];
[standardDefaults setFloat:location.coordinate.longitude forKey:@"locationLongitude"];
weatherCalled = 1;
}
- (void)getWeather:(CLLocation *)location {
if (weatherCalled == 0) {
CLLocationCoordinate2D currentLocation = location.coordinate;
NSLog(@"%f, %f", currentLocation.latitude, currentLocation.longitude);
GetWeather *weather = [[GetWeather alloc] init];
[weather getWeatherAtCurrentLocation:currentLocation];
self.currentLocation.text = weather.currentLocation;
self.currentTemp.text = weather.currentTemperature;
NSDate *today = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterNoStyle;
formatter.timeStyle = NSDateFormatterShortStyle;
self.lastRetreived.text = [formatter stringFromDate:today];
weatherCalled = 0;
} else {
NSLog(@"Weather Check Already in Progress");
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
GetWeather.h
#import
#import
@interface GetWeather : NSObject
@property (strong, nonatomic) NSString *currentLocation;
@property (strong, nonatomic) NSString *currentTemperature;
- (void)getWeatherAtCurrentLocation:(CLLocationCoordinate2D)coordinate;
@end
GetWeather.m
#import "GetWeather.h"
@implementation GetWeather
static const NSString *WORLD_WEATHER_ONLINE_API_KEY = @"YOUR_API_KEY";
- (void)getWeatherAtCurrentLocation:(CLLocationCoordinate2D)coordinate {
NSString *urlString = [NSString stringWithFormat:@"http://api.worldweatheronline.com/free/v1/weather.ashx?q=%f,%f&format=json&num_of_days=1&includelocation=yes&key=%@", coordinate.latitude, coordinate.longitude, WORLD_WEATHER_ONLINE_API_KEY];
NSURL *weatherURL = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:weatherURL];
[self parseJSONData:data];
}
- (void)parseJSONData:(NSData *)data {
NSError *error;
NSDictionary *parsedJSONData = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
NSDictionary *weather = [parsedJSONData objectForKey:@"data"];
// Nearest Area and setting currentLocation property.
NSDictionary *nearestArea = [weather objectForKey:@"nearest_area"];
NSArray *array = [nearestArea valueForKey:@"areaName"];
NSDictionary *currentLocation = [array objectAtIndex:0];
NSArray *currentLocationString = [currentLocation valueForKey:@"value"];
self.currentLocation = currentLocationString.lastObject;
// Getting current temp in degrees C
NSArray *currentCondition = [weather objectForKey:@"current_condition"];
NSDictionary *tempDictionary = [currentCondition objectAtIndex:0];
NSArray *tempInC = [tempDictionary valueForKey:@"temp_C"];
//NSArray *weatherInC = [currentCondition objectForKey:@"temp_c"];
NSString *temperature = [NSString stringWithFormat:@"%@", tempInC];
self.currentTemperature = [temperature stringByAppendingString:@"C"];
}
@end
AppDelegate.h
#import
#import
@interface AppDelegate : UIResponder
@property (strong, nonatomic) UIWindow *window;
@end
AppDelegate.m
#import "AppDelegate.h"
#import "GetWeather.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
return YES;
}
- (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)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (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:.
}
-(BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return true;
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"Fetch started");
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
// Get Current Location from NSUserDefaults
CLLocationCoordinate2D currentLocation;
currentLocation.latitude = [standardDefaults floatForKey:@"locationLatitude"];
currentLocation.longitude = [standardDefaults floatForKey:@"locationLongitude"];
// GetWeather for current location
GetWeather *getWeather = [[GetWeather alloc] init];
[getWeather getWeatherAtCurrentLocation:currentLocation];
// Set up Local Notifications
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
NSDate *now = [NSDate date];
localNotification.fireDate = now;
localNotification.alertBody = [NSString stringWithFormat:@"Temperature in %@ is %@.", getWeather.currentLocation, getWeather.currentTemperature];
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.applicationIconBadgeNumber = [getWeather.currentTemperature intValue];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
completionHandler(UIBackgroundFetchResultNewData);
NSLog(@"Fetch completed");
}
@end
Kaushal Pal says
YOUR POST ARE UNDOUBTEDLY THE BEST IN ALL ASPECTS….KEEP IT UP ..
Drew Pitchford says
This is great. I have a question though. For setMinimumBackgroundFetchInterval:… If I were to put in 60 seconds, would that mean the app would actually be considered for fetch every minute? Or would iOS still override it and just do it according to usage patterns?
Matthew says
I haven’t done full testing on that yet but I believe Apple would aim for that, but if your app doesn’t retrieve anything useful too many times then it will slow it down. There’s a completion handler which lets it know if the fetch was successful which I think it works with.
Szilveszter Molnár says
good tutorial, however you should read about ‘solid’
Matthew says
What do you like about solid? Assuming you are referring to MVC and separating in to classes each of the work, I fully agree although for tutorial purposes it works best to keep everything in a single class, maybe a couple of classes so that its easier to see the code working together. But, for production you definitely want to refractor etc… and separate the view from the controller and the model (although I prefer a different way to Apples implementation of MVC).
ÐиÑалий Ðванов says
Nice, thank you!
erwin says
thanks for sharing… useful as a starter
zhanglv says
Good tutorial ! Thank you!
Zhong says
Thanks for your useful posting.
I have one question for now.(I am not familiar with app background mode)
I am going to write the GPS tracking app to be executed in foreground and background mode as well.
So, even though app is in background mode, it should continue to track until the user stop.
I mean, the app should not be suspended.
performFetchWithCompletionHandler function can continue the running of the app?
Hope to hear from you soon.
Thanks for your time.
rohit rananaware says
Can possible to send sms in background mode
Matthew says
You cannot send SMS in background mode. I suspect this is because of potential abuse where a phone could be programmed to send out SMS messages to premium numbers all while the user was unaware of what was happening.