nevan.net


Using Core Location With Today Extensions

Today extensions or “Widgets” are a new feature of iOS 8 which allow developers to provide an app’s core functionality into the Notification Center. If an app relies on location, the Widget will also need to be able to use location.

Getting the user’s location in a Widget is a common use case. The app might need to check the weather in the current location, or put up a notice about interesting places nearby. Fortunately it’s just as easy to get location in a Widget as it is in a full app.

The minimum steps to use location in a Widget are as follows:

In the main app:

  • Add an NSLocationWhenInUseUsageDescription key with a description string in the container app’s Info.plist


In the Widget:

  • Import the CoreLocation framework
  • Create a location manager (‘CLLocationManager’)
  • Request authorization and start the location manager
  • React to the delegate methods

Add the reason for using location in the Info.plist along with the key and that text will be shown to the user when they are asked for permission.

Requesting permission

If the container app relies on location, you can request the user’s permission there and the Widget will automatically be granted the same permission. If the user opens the widget before opening the main app, location authorization will be requested there and subsequently available in the main app without prompting.

There’s no need to request background (always) location for the Widget to work, WhenInUse authorization is enough. A Widget only ever works in the foreground and cannot be woken for background tasks. If your app needs Always authorization, request that type in the Widget too.

Caching location

If you want your Widget to acquire a very quick location fix, you can request background location for your main app, use the significant location change service, and share that location in a shared container that the Widget can quickly read from when its first brought up.

Implementation

Here’s an implementation for a Today Extension that will request WhenInUse location authorization. It sets a label’s text to the latitude and longitude of the current location. If there are errors, it repurposes the label to show the error as an alternative to NSLog() debugging.

#import "TodayViewController.h"
#import <NotificationCenter/NotificationCenter.h>
@import CoreLocation;

@interface TodayViewController () <NCWidgetProviding, CLLocationManagerDelegate>

@property (strong, nonatomic) CLLocationManager *locationManager;
@property (weak, nonatomic) IBOutlet UILabel *locationLabel;

@end

@implementation TodayViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    // iOS 8 and higher
    if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
        // TODO: Remember to add the NSLocationWhenInUseUsageDescription plist key
        [self.locationManager requestWhenInUseAuthorization];
    }

    [self.locationManager startUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *location = [locations lastObject];

    // Output the time the location update was received
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateStyle = NSDateFormatterNoStyle;
    dateFormatter.timeStyle = NSDateFormatterMediumStyle;
    NSString *timeString = [dateFormatter stringFromDate:[NSDate date]];

    // Create a string from the CLLocation
    NSString *latLonString = [self stringFromCoordinate:location.coordinate];

    // Set the main label of the Widget to the date and coordinate
    self.locationLabel.text = [NSString stringWithFormat:@"%@ %@", timeString, latLonString];
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    self.locationLabel.text = error.localizedDescription;
}

- (NSString *)stringFromCoordinate:(CLLocationCoordinate2D)coordinate
{
    return [NSString stringWithFormat:@"%f %f", coordinate.latitude, coordinate.longitude];
}

- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler
{
    completionHandler(NCUpdateResultNewData);
}

@end