LearnCreating a Today Extension with iOS 8

Pasan Premaratne
writes on January 20, 2015

One of the interesting new features introduced with iOS 8 this past WWDC was Extensions. In this post, we’re going to learn how to create a simple extension for an existing app. Now, before we get into the nitty gritty and build our extension, let’s spend a quick second talking about what extensions are exactly.

According to their marketing site, extensions, available for both iOS 8 and OS X Yosemite, give your users access to your app’s functionality throughout the OS rather than just when interacting with the app. More specifically:

an app extension lets you extend custom functionality and content beyond your app and make it available to users while they’re using other apps or the system.

An extension is used to enable a specific task from your app in different contexts. The easiest way to understand this is through the use of social media sharing options enabled in iOS. If you have the Facebook app installed, then tapping on the share button on a webpage brings up a Facebook option built right into the share sheet.

Another use is to quickly get information from your Notification Center. If you’re a sports fan and happen to have an app like ESPN installed, then you can use the ESPN extension, also called a widget, to quickly view your sports scores right from the notification center screen.

As evident from the examples I just mentioned, there are few different ways we can build extensions and each of these are specific kinds, so let’s take a look at all the different ones.

Extension Points

iOS defines several types of extensions, each of which is tied to an area of the system. A system area that supports extensions is called an extension point. There are different kinds of extension points and the type of extension point you choose is based on the functionality you want to provide to your users. Each extension point also defines usage policies and provides APIs that you use when you create an extension for that area. So what are the different extension points we can use:

  • Today: Get a quick update or perform a quick task in the Today view of Notification Center.(A Today extension is called a widget)
  • Share: Post to a sharing website or share content with others
  • Action: Manipulate or view content originating in a host app
  • Photo Editing (iOS): Edit a photo or video within the Photos app
  • Document Provider: Provide access to and manage a repository of files.
  • Custom Keyboard (iOS): Replace the iOS system keyboard with a custom keyboard for use in all apps

That’s quite a bit! Each of these different extensions, while sharing much of the same underlying architecture, have different implementations so to make things easier, we’ll focus on a single kind, building a Today extension, for this post. We’ll explore the rest in future posts in the series.

Getting Started

The first thing to know when building an extension is that an app extension is different from an actual app. Although an extension requires a containing app to deliver it, each extension is a separate binary that runs independently of the app.

For the purposes of this tutorial, I put together a simple weather app (rite of passage to becoming an iOS developer) that you can download (only Objective-C for now) and use as a starter file. Couple things to note: This is a quick and dirty app and as such robust error handling and networking code is noticeably missing. Additionally, iOS 8 handles location quite differently from prior versions and I only put together the bare minimum needed to get a location fix. If you’re using any of this code out in the wild, make sure you cover your bases.

For the purposes of this project, we’re going to be using the Forecast.io weather API to get our weather data. Before you can build and run the app, you need to sign up for a dev account and obtain an API key. Once you open the project, in the ViewController.m file , at the top I’ve declared a constant kForecastApiKey. Set this constant to your API key so you can use it in the project. Without it, you won’t be able to query the Forecast API and the app won’t update.

Once you’ve added the key in, hit Cmd + R to build and run the project.

Rainy

Pretty simple, once the app gets a location fix, you should see the location, temperature and icon displayed. For some of you it may not work on your first try and this is because simulating location updates on the iOS simulator isn’t always the easiest thing. Under the Debug menu of the iOS Simulator, make sure that a location option is selected under the Location sub menu. Now, I’m not going to go over any of this code, so if you’re totally new to iOS development and would like to know how to build a weather app, check out my Build a Weather App with Swift course on Treehouse that teaches you just that.

Ok, let’s get into the code. If you’ve got the app open and you do a little bit of inspecting you will notice that most of the app logic is seemingly missing and that’s because it is linked as a framework. If you check either in the frameworks group in the project or the Link Binary With Libraries section of the project’s Build Phases, you should see a WeatherKit framework linked. The actual framework is quite trivial and doesn’t contain a lot of code aside from making an API request and handing off the JSON data but there is a very specific reason we chose to include this code as a framework. Today extensions are created by adding a new target to an app. App targets cannot share code directly but by modularizing the forecasting code as a framework, I can utilize it between multiple targets allowing me to avoid repeating code. The “how” part of creating a framework is outside the scope of this post, but here’s an excellent tutorial that you can use for your purposes. Now that that’s out of the way, let’s create a Today extension!

Adding a New Target

As discussed earlier, to add a Today extension to our app, we need to add a new target. Head over to the project settings section and you should see a colum on the left side that lists the project and its targets (you might have to click the drawer icon to bring it out).

Target Selection

Click on the plus button on the bottom and under iOS select Application Extension. Xcode provides a bunch of different templates to create the various extension points; select the last option – the Today extension. Let’s name ours RainyToday.

You’ll notice that the Project to which the target will be added is the project we’re currently working with and the extension will be embedded in the containing application. Also note that the extension has a distinct bundle identifier based on the one of the containing application, com.pasanpremaratne.Rainy.RainyToday. Ok let’s hit create.

This does a couple things. First, Xcode creates a new scheme and asks if you want to activate it – click yes.

Second, in our list of Targets, we have a new one called RainyToday. We also have a new folder with the same name in our Project Directory that contains a couple files – a TodayViewController, which is the main class we’ll work with, an interface file, called MainInterface.Storyboard and an Info.plist file (in the Supporting Files group). At its simplest, a Today extension, or widget as it’s called, is no more than a view controller and storyboard file. Let’s run the extension to see what we’ve got by default.

Hit Cmd+R to run the app. Once the app loads, pull down from the status bar to bring the notification center on the screen. When you run the project for the first time, you wont see your extension and that’s because you need to add the widget to the Today screen. At the bottom of the screen there’s an Edit button and below that it should say “1 new widget available”. Tap the Edit button and add your widget to the Today list.

Let’s see what it looks like. A Today widget template is pretty basic, all we have is a “Hello, World” label. Let’s modify it. Head over to the MainInterface.Storyboard file and let’s create an outlet so we can programmatically change this label from our view controller. Using the assistant editor, control drag from the label to the TodayViewController.h file and create an outlet called weatherLabel.

Now, in the implementation file, add the following line of code in the viewDidLoad method.

self.weatherLabel.text = @"Hmm, wonder what the weather is?";

Let’s run the extension again, but this time, we’re going to change the scheme to the extension rather than the containing app.

If we run the containing app, the debugger is attached to the main app and not the widget, which is part of the system and not the app. To attach the debugger to the widget, make sure to select the correct scheme and hit Cmd-R. Xcode presents you with a popup asking you to choose an app to run. Select Today.

Just as expected (although it might take a second) the label text has changed. Fundamentally, an extension is no different in its implementation from our containing app. We can add controls, call methods and execute our code.

Creating a Weather Extension

We’re going to keep our widget pretty simple for this tutorial and just display the current temperature at our location. Fortunately, I’ve done most of the work and the logic for the app is included in the WeatherKit framework. First, let’s retrieve our location for the widget to use.

Let’s add a property to our view controller class to store our location manager object.

@property (strong, nonatomic) CLLocationManager *locationManager;

To use the CLLocationManager class, we need to import the CoreLocation framework, so add the following line of code to the top of the view controller header file.

#import <CoreLocation/CoreLocation.h>

Next, in the viewDidLoad method, get rid of the the code we just added to change the label text and copy paste the following block of code.

//Set up location manager and start monitoring location
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;

if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
    [_locationManager requestAlwaysAuthorization];
}

[_locationManager startMonitoringSignificantLocationChanges];

//Set up forecaster kit
_forecaster = [[Forecaster alloc] initWithAPIKey:kForecastApiKey];

First we set up the location manger, assign our view controller as the delegate and set the location parameters. To allow our view controller to act as a delegate we need it to conform to the CLLocationManagerDelegate protocol. Add <CLLocationManagerDelegate> to the header file after the class declaration.

Next, we check to see if the user has authorized our containing app, and requisition authorization if not.
Once we have authorization, we start updating the location. Finally, set up an instance of the Forecaster class and init with our forecast.io API key. Let’s start by adding a property to the TodayViewController.

@property (strong, nonatomic) Forecaster *forecaster;

We also need to add a forward declaration of the Forecaster class. In the header file, add the following code to the top, right below the import statement.

@class Forecaster;

We’re going to need to add our API key to the file as a constant here as well so add the following line of code to the top of the TodayViewController.m file, just under the import statement.

NSString *const kForecastApiKey = @"/*Add your API key here*/";

The Forecaster class is my wrapper around the Forecast.io API and is included in the WeatherKit framework. To use it we need to link the binary with the WeatherKit library. Head over to the project settings and with the RainyToday target selected on the left hand, click on the Build Phases tab. To use the WeatherKit framework in our widget, we need to add it to the Link Binary With Libraries section. Expand the section and then drag the WeatherKit framework from the project navigator (in the Frameworks group) to link it. Back in the TodayViewController.m file, add the following import statement to the top.

#import <WeatherKit/Forecaster.h>

Don’t worry about the class too much. There’s just one method we’re going to use here:

(void)getForecastForLatitude:(double)lat longitude:(double)lon success:(void (^)(NSDictionary *JSON))success failure:(void (^)(NSError *error, id response))failure;

To obtain the current weather forecast, we pass in a latitude and longitude value that we obtain from our location manager. If the forecast query works, we get an NSDictionary with JSON data in a success block. Otherwise we get an error and the HTTPResponse in a failure block.

To put this method to work, we need to implement a CoreLocation delegate method, locationManager:didUpdateLocations:. Ever =ytime the location manager gets a location update, it stores this as an array of CLLocatio n objects. The array always contains at least one location object and the most recent location is stored at the end of the array. Add the locationManager:didUpdateLocations: method to the view controller implementation file and inside the method definition, paste the following code:

CLLocation *weatherLocation;
if (locations.lastObject != nil) {
    weatherLocation = locations.lastObject;
} else {
    weatherLocation = manager.location;
}

We start by creating a local variable to store our current location. Then we use the coordinates from this object in the getForecastForLatitude:longitude:succes:failure:method.

[self.forecaster getForecastForLatitude:weatherLocation.coordinate.latitude longitude:weatherLocation.coordinate.longitude success:^(NSDictionary *JSON) {
    NSDictionary *currentWeather = [[NSDictionary alloc] initWithDictionary:JSON[@"currently"]];
    dispatch_async(dispatch_get_main_queue(), ^{
        self.weatherLabel.text = [NSString stringWithFormat:@"The current temperature is %ld farenheit", (long)[currentWeather[@"temperature"] integerValue]];
    });

} failure:^(NSError *error, id response) {
    NSLog(@"%@", error);
}];

If the forecast query worked, the method returns a success block containing a JSON payload formatted as an NSDictionary. From the JSON dictionary, we obtain the current weather dictionary using the key currently. We can then obtain the current temperature from the resulting dictionary using the temperature key. As an aside, all this information is covered in the Forecast docs or if you want a step by step guide, you can take my course as well.

Once we have the current temperature, let’s modify the text of the weatherLabel to display the temperature in the widget. Remember any UI updates need to be done on the main view, so we use dispatch_async to add our code to the main queue.

For the sake of this post I’m also going to just log the failure to the console. Remember, handling failure is quite important so please don’t do this for a real app. Another important thing to note, if you run the project now, your weatherLabel might not update and that’s because we’ve set the location manager to update only for significant location changes. My location did not update until I set the manager to monitor for any location change. To do that replace [_locationManager startMonitoringSignificantLocationChanges]; with [_locationManager startUpdatingLocation]; in the viewDidLoad method. Beware, however that depending on the simulator’s location option that you specify, you might have a significant amount of location updates, which means a lot of calls to the Forecast API with the way our code is structured. I left my extension running for a bit while writing this post and racked up 2000 calls (more than 1000 calls per day and you have to pay em!).

Run the extension and you should have the local weather! Now that looks good, but there’s some empty space below our label and our UI could use some tightening up.

Tighten Up Our UI

Head over the the MainInterface.Storyboard file and select the label. Left align it and move the label all the way to the left margin. Change the default text to “Hmm, what’s the weather like..”, the font color to white and resize the view. You’ll need to update the constraints as well to take the changes into account. Run the app again to see your changes.

That looks a lot better but we still have some unwanted space at the bottom of our widget. To get rid of that, we need to customize the appearance of our widget in code. Our TodayViewController conforms to the NCWidgetProviding protocol, which let’s us customize the widget. Implement the widgetMarginInsetsForProposedMarginInsets method and return a custom margin to tighten up our view.

(UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
    defaultMarginInsets.bottom = 10.0;
    return defaultMarginInsets;
}

Voila! You have your first today extension! There’s a lot more to learn about widgets – for example, once a user clicks on our widget, they should be redirected to the container app. This blog post is getting quite long however, so I’ll let you figure out how to implement that.

Another thing to note: Quite often there were instances where changes I made weren’t reflected in the widget on the simulator or my iPhone. Removing the widget from the Today view and running it again seemed to work every time so if you have any issues, try removing the widget and re-running.

 

2 Responses to “Creating a Today Extension with iOS 8”

  1. Some of the images seem to be missing?

  2. Very informative article, appreciate it.

Leave a Reply

Want to learn more about iOS?

iOS is the operating system that powers iPhones and iPads. Learn the language, tools and frameworks to build interactive apps on the iOS platform.

Learn more