Crash Course In iOS 8 Widgets
I'm really happy that we can publicly talk about iOS 8 in its beta state. That said, this post cannot contain screenshots because of some remaining limitations in the NDA. Also, since this was written at the time of Beta 1, what I discuss here has a chance of completely changing. It's well after midnight as of the original writing of this post, so sample code will follow at some point. That said, now onto the post...
There's quite a dearth of documentation on widgets, a new feature on iOS 8. This is a tutorial that will hopefully help you avoid some of the pain points I experienced so that you can get up and running.
First off, debugging extensions of any kind appears to be broken. In some cases I setup a debug label to show stuff on my widget to help me troubleshoot. There is a workaround below.
Creating a widget involves creating a brand new target. You can go to your Xcode project file and add a new target. A dialogue window will drop down and one of the target categories will be Application Extension. Select that, then Today Extension. Give it a product name and select the target that you want to embed it in.
Designing the Widget
When you create the new widget target, you'll get an Info.plist, a view controller subclass, and a storyboard. To get running quickly, you can just customize the view controller in the storyboard, adding additional programmatic setup in the code files. Creating a widget is very, very easy and leverages your existing Cocoa Touch skills.
Sharing Data
Naturally, you're going to want to surface content from your main application inside a widget. But even though the widget will be embedded in your app, it is its own app and has its own sandbox. The individual sandboxes cannot mix, but Apple has created a shared container that both applications can read from and write to. This shared container is created by enabling App Groups. This is a feature that you can turn on in the Capabilities tab of your target properties. When enabled, you can create a new App Group and it should start with group. This capability must be enabled on both your application and widget targets and the group ID must match.
Once this is enabled on both apps, you'll need to both read from and write to the shared container. In iOS 7, Apple added an API to NSFileManager called containerURLForSecurityApplicationGroupIdentifier:. This will construct an NSURL instance that can be used to reference files inside the container.
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.companyname.app.widget"];
containerURL = [containerURL URLByAppendingPathComponent:@"data.plist"];
Launching The App
Another feature that may be very useful for your users is launching your app from the widget in the Notification Center. There is a new set of APIs for doing just this that are specific to widgets. There is a new class called NSExtensionContext that exposes a method openURL:completionHandler: that can be used to launch the custom URL in your app. Just pass it a valid NSURL instance that your app responds to, and away it goes.
NSExtensionContext *context = self.extensionContext;
[context openURL:deepDiveURL completionHandler:^(BOOL success) {
// logic here
}];
Debugging
Debugging currently has some...limitations...that require you to attach to the process manually, if running on simulator. Follow these steps thanks to Daniel Kao:
- Build and Run your widget in simulator
- Go to Notification Center, include your widget in notification center, then press Done button
- Your widget should appear now
- Go back to Xcode, Debug, Attach to process by Process Identifier(PID) or Name, enter widget process ID or process name (should be the bundle identifier of your widget)
If you're doing this on device, you can just hit the run button, at which point you'll be asked to choose an app to run. In this case, you'll want to choose ‘Today', since that is the app the runs the extensions for the Today view in Notification Center. I have found that debugging widgets on-device is very unstable, often crashing SpringBoard. Pick you poison, I suppose.