
With the release of iOS 8, developers have the opportunity to create their own widgets for the Today screen. For the time being, the API has not been finally settled, there is a
Known Issue and many moments not described in the documentation. But if you still want to make your widget, then I ask for the cut (note, Swift is used in the examples).
Extensions
IOS has a new concept - extensions. Extensions allow you to make available some of the content and functionality outside the application.
The part of the system that supports extensions is called extension point. The following extension points are available for iOS:
- Today (Notification Center) - quickly perform some action or get information through the Today screen in the Notification Center
- Share - share content with friends or in a feed on some site.
- Action - view or manage content within the context of another application
- Photo Editing - edit a photo or video inside the Photos application
- Storage Provider - select a document from the set of documents available to the current application
- Custom Keyboard - replace the native iOS keyboard with your own for use in all applications
Extensions can only be distributed in the extension container that is used by the bundle of a regular application. One container may contain several extensions.
')
It should be noted that extensions are a special kind of binary files. This is not an application!
Unfortunately, extensions do not support the App to App IPC (pipes, sockets, ...), and therefore you need to use the familiar [UIApplication openURL:] (everyone does not work for extensions, see
Known Issue ) or, for example,
App Group .
Each extension runs in a separate process. Thus, the same extensions in the context of different applications are different processes, and you can not worry about synchronization problems.
Extensions documentation is
here .
Widgets
Widgets are extensions that display information in the Notification Center on the Today screen and, therefore, are designed to show the information that is important at the current moment. When a user opens Today, he expects the information of interest to him to be instantly available.
The widget becomes available after the user installs the application containing the widget (now it happens that the widget is not installed after the first launch of the application, after all, this is still a beta). To add a widget, you need to open the Today screen in the Notification Center, click the Edit button and add the desired widget.
The connection between the container and the widget is through
NotificationCenter.framework .
In essence, a widget is a UIViewController, which is well known to any iOS programmer. Accordingly, when creating widgets, you can use the previously accumulated knowledge. For example, if you need to perform some action before displaying the widget, you should override viewWillAppear, etc.
So that the widget always looks up to date, iOS sometimes makes the widget snapshots. When the widget becomes visible again, the last snapshot is shown first, and only then the real widget window. In order for the widget to update its state before snapshot, the
NCWidgetProviding protocol is
used .
protocol NCWidgetProviding : NSObjectProtocol {
When a widget calls widgetPerformUpdateWithCompletionHandler, it must refresh its window and then call the completionHandler block with an argument equal to one of the following constants:
- NCUpdateResultNewData - new content requires updating the window
- NCUpdateResultNoData - the widget does not need an update
- NCUpdateResultFailed - an error occurred during the update process
Since users are waiting for an instant response from the Notification Center, and the system performs snapshots, the widget simply has to keep its previous state, that is, cache the data it needs to work.
Notification Center determines the width of the widget, while the widget itself determines its height. To determine the height, the widget can use Auto Layout or the
preferedContentSize property of the
Executor UIViewController.
override func viewDidLoad() { super.viewDidLoad() self.preferredContentSize.height = 350 }
It turns out that widgets have the following requirements:
- ensure that the displayed content is relevant
- properly respond to user actions
- consume as few resources as possible (iOS can kill a widget if it consumes a lot of memory)
Please note that the UI for widgets has the following limitations:
- can not show the keyboard
- cannot use controls that work with gestures (for example, UIDatePicker)
- no maps can be displayed yet (now this item is listed in the Known Issue - Map tiles.)
Since the keyboard is inaccessible, the user must be able to configure the widget in the container application.
Interaction with the widget
To update the information displayed by the widget, there is a class
NCWidgetController .
An instance of this class has a single setHasContent method: forWidgetWithBundleIdentifier:, which sends a message to the widget that it should update the information.
Used as follows:
NCWidgetController.widgetController().setHasContent(true, forWidgetWithBundleIdentifier: "com.e-legion.Traffic.Widget")
This class can be used from a widget and a container application.
Exchange data with a container application
To communicate with the container, an
NSExtensionContext object
is available via the
extensionContext property of the UIViewController.
class NSExtensionContext : NSObject {
Ie to open the container application from the widget, the container must register the scheme (for example, “traffic: //”), and add to the code of the widget
self.extensionContext.openURL(NSURL(string: "traffic://"), completionHandler: nil)
.
By default, the iOS security system prohibits the exchange of data between the container application and the extension. To enable data exchange, you need to add the container target and extensions to one
App Group .

As a result, the file entitlements will have the following contents:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.application-groups</key> <array> <string>group.96GT47C53G.traffic</string> </array> </dict> </plist>
Check out the 96GT47C53G. This is Development Team ID. You can view it in your
profile . To run on the simulator, you can use any value, for example, group.traffic, ...
Now, using the
containerURLForSecurityApplicationGroupIdentifier method, you can get the path to the shared folder and store application-specific data there.
NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.96GT47C53G.traffic")
Example
So, try to create a widget that will show the map with traffic jams. We will receive information about traffic jams in the form of a picture using
the Yandex.Maps API .
Example of a link to get a picture:
http://static-maps.yandex.ru/1.x/?ll=30.35,59.9690273&spn=0.01,0.2&size=300,250&l=map,trfActually, lat and lon is the center of the map, and spn is the length of the map display area in degrees.
The source code for the project is available on
GitHub .
Create a container application
The task of the container is to give the user the ability to configure the widget, namely, select an area on the map. Thus, the application will contain a MapView to select a region and a “Set frame” button that will transfer this region to the widget.

The application should allow the user to select lat, lon, spn parameters and pass them to the widget. The following code does this:
@IBAction func updateWidgetButtonTapped(sender : AnyObject) { var dict : NSMutableDictionary = NSMutableDictionary() dict["spn"] = self.mapView.region.span.latitudeDelta dict["lat"] = self.mapView.region.center.latitude dict["lon"] = self.mapView.region.center.longitude var dictUrl : NSURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.96GT47C53G.traffic").URLByAppendingPathComponent("settings.dict") dict.writeToFile(dictUrl.path, atomically: true) NCWidgetController.widgetController().setHasContent(true, forWidgetWithBundleIdentifier: "com.e-legion.Traffic.Widget") }
Adding a widget
Adding a widget boils down to adding a new target.
Click File-> New-> Target and select iOS-> Application Extension-> Today Extension.

Now a stub for the widget has been added to the project. But if you now try to use your widget, then nothing will come of it. The widget will crash. To fix this, you need to add the following method to TodayViewController:
init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) }
Notice the Info.plist file from the template. It contains the NSExtension key, in which some parameters of the widget are defined.
<key>NSExtension</key> <dict> <key>NSExtensionMainStoryboard</key> <string>MainInterface</string> <key>NSExtensionPointIdentifier</key> <string>com.apple.widget-extension</string> </dict>
NSExtensionMainStoryboard stores the name of the storyboard in which the controller for the widget is stored. The controller can be specified explicitly by replacing the NSExtensionMainStoryboard key with the NSExtensionPrincipalClass and using the name of the controller as the value.
Each widget has a slight left shift. If you want to get rid of it, then you need to return the desired UIEdgeInsets in the widgetMarginInsetsForProposedMarginInsets method.
func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets { return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) }
The widget code is quite simple. There is an updateMap method that performs map updates. The update takes place when the widget starts to display (viewWillLoad), the button is clicked and widgetPerformUpdateWithCompletionHandler is called. The widget receives information about the displayed area through
containerURLForSecurityApplicationGroupIdentifier .
Conclusion
Widgets are a very cool thing, but for now everything is damp. Sometimes crashes occur, sometimes the widget is not visible, etc. But most of all there is not enough documentation. Soon all this will be fixed and it will be possible to enrich your applications with new functionality, but in the meantime you can play around with what you have.