Note from Ray: This is an abbreviated version of a chapter from iOS 8 by Tutorials released as part of the iOS 8 Feast to give you a sneak peek of what’s inside the book. We hope you enjoy!
iOS 8 introduces a new concept called App Extensions: a way for you to share your app’s functionality with other apps, with to the OS itself.
One of these types of extensions are Today Extensions, also known as Widgets. These allow you to present information in the Notification Center, which can be a great way to provide immediate and up-to-date information that the user is interested in.
In this tutorial, you’ll write a Today Extension that renders the current market price of a Bitcoin based on the United States Dollar.
Never has it been so easy to deliver valuable information so quickly to your users. Let’s get started!
Introducing Bitcoin
If you’re not familiar with Bitcoin, the short explanation is that it’s a digital cryptocurrency that’s still in its infancy. Aside from using it for peer-to-peer exchanges and purchases, Bitcoin trading allows the user to exchange it for a number of other cryptocurrencies like Dogecoin and Litecoin, and flat currency such as the US Dollar and the Euro.
As a relatively new currency, its market value fluctuates by the minute; there have been huge peaks and troughs in its short lifetime. Thus, it’s a perfect candidate for a Today Extension since investors will want up-to-the-second price quotes!
Introducing Crypticker
Since you’re writing an extension, you’ll first need a host app to extend; meet Crypticker.
Crypticker is a simple app that displays the current Bitcoin price, the difference between yesterdays price and the current price, as well as a price history chart. The chart includes 30 days of history; sliding your finger on the chart reveals the exact price for a specific day in the past.
The extension will contain all of these features, except for touching the chart to see the price for a specific day. There are some limitations within Today Extensions, especially when it comes to swiping. The swipe gesture often triggers swiping between the Today and Notifications sections within Notification Center, so it doesn’t really provide the best or most reliable user experience.
Getting Started
Download the Crypticker starter project to get started. The project contains the entire Crypticker app as described above. This tutorial will not focus on the development of the container app, so you may be pleasantly surprised to find this tutorial to be rather succinct. After all, you’re writing an extension, not an entire app.
Build and run. Please note that you’ll need a working Internet connection to pull the real-time prices from a web service.
The app looks very similar to the screenshot above; the data displayed will, of course, depend on how the Bitcoin market looks right now. Touching the chart near the bottom of the view will draw a line and display the price for the relevant day.
BTC widget
For the unfamiliar, BTC is shorthand for Bitcoin; much like USD stands for United States Dollar. The Today Extension will render a scaled down version of Crypticker’s primary view.
Theoretically, the Crypticker app has the ability to show pricing for multiple Cryptocurrencies, but your extension is specific to BTC. Therefore, its name shall be BTC Widget.
Note: Extensions, by nature, have just one simple purpose. If you wanted to provide information for another cryptocurrency, like Dogecoin, it would be best to package a second widget with the app or design your UI appropriately, perhaps like the Stocks widget.
By the end of the tutorial, your Today Extension will look something like this:
Add a Today Extension target
Extensions are packaged as a separate binary from their host app. So you’ll need to add a Today Extension target to the Crypticker project.
In Xcode’s Project Navigator, select the Crypticker project and add a new target by selecting Editor\Add Target… When the template picker appears, choose iOS\ Application Extension, and then Today Extension. Click Next.
Set the Product Name to BTC Widget, and verify that the language is Swift, the project is Crypticker and Embed in Application is also Crypticker. Click Finish.
When prompted activate the BTC Widget scheme. As the text indicates, another Xcode scheme will be created for you.
Congratulations! BTC Widget will now appear in your list of targets.
Make sure you select BTC Widget, then the General tab, and then press the + button under Linked Frameworks and Libraries.
Select CryptoCurrencyKit.framework and click Add.
CryptoCurrencyKit is a custom framework used by the Crypticker app to retrieve currency prices from the web. Luckily for you, the incredibly kind and thoughtful developer behind Crypticker modularized the code into a framework, so that it can be shared between multiple targets :]
In order to share code between a host app and its extensions you must use a custom framework. If you don’t, you’ll find yourself duplicating a lot of code and violating an important rule of software engineering: DRY – or, Don’t Repeat Yourself. I’ll say it again: “Don’t repeat yourself”.
At this point, you’re ready to begin implementing the extension.
Notice there’s now a group in the Project navigator named after your new target, BTC Widget. This is where the Today Extension’s code is grouped, by default.
Expand the group and you’ll see there is a view controller, a storyboard file and an Info.plist file. Its target configuration also tells it to load its interface from MainInterface.storyboard, which contains a single view controller with the class set to TodayViewController.swift.
You’ll notice some files you might expect to see are missing from the Today Extension template; like an app delegate for instance. Remember that extensions run inside another host app, so they don’t go through the traditional app lifecycle.
In essence, the lifecycle of the extension is mapped to the lifecycle of the TodayViewController.
Open MainInterface.storyboard. You’ll see a dark colored view with a light Hello World label. Today Extensions are most legible when they have a clear background and light or vibrantly colored text in order to harmonize with the dark, blurred background of Notification Center.
Make sure the BTC Widget scheme is selected in Xcode’s toolbar and build and run. This will cause a window to appear asking which app to run. Xcode is asking you which host app to run. Choose Today. This tells iOS to open Notification Center in the Today view, which in turn launches your widget. Notification Center is effectively the host app for Today Extensions.
This also causes Xcode to attach its debugger to the widget’s process.
Behold your widget. Cool, right? Whilst this is super-exciting stuff, the widget clearly needs a little work. It’s time to make it do some interesting things!
Note: You might notice a lot of Auto Layout errors printed to the console when launching the widget. This is an issue with the Xcode template, and will hopefully be resolved by Apple in the future. Don’t worry though, since you’ll be adding your own interface, including Auto Layout constraints.
Build the Interface
Open MainInterface.storyboard and delete the label. Set the view to 150pts tall and 320pts wide in the Size Inspector. Drag a Button, two Labels and a View from the Object Library onto the view controllers view.
- Position one of the labels in the top left corner, and in the Attributes Inspector set its Text to $592.12 and its Color to Red: 66, Green: 145 and Blue: 211. This will display the current market price.
- Position the other label to the right of the one you’ve just set up, but leave a margin to the right for the button. In the Attributes Inspector set its Text to +1.23 and its Color to Red: 133, Green: 191 and Blue: 37. This will display the difference between yesterdays price and the current price.
- Move the button to the upper right of the view, and in the Attributes Inspector set its Image to caret-notification-center and delete its Title.
- Finally, position the empty view below the two labels and the button, stretch it so it’s bottom and side edges are touching the containing view and set its Height to 98. In the Attributes Inspector set its Background to Clear Color, and in the Identity Inspector set its Class to
JBLineChartView
.
Note: There is a class named JBLineChartDotView
that Xcode may suggest when typing, verify that you chose JBLineChartView
.
The view and Document Outline should now look something like this:
Note: The view shown here has a white background for the purpose of visibility in this book. Your view will actually have a dark grey background that simulates how your view appears within Notification Center.
Don’t worry about laying things out exactly as shown, as you’ll soon be adding Auto Layout constraints to properly define the layout.
Now, expand the Crypticker group in the Project Navigator and select Images.xcassets. In the File Inspector, add the asset catalog to the extension’s target by checking the box to the left of BTC Widget.
This causes Xcode to include the image asset catalog from the Crypticker target in your BTC Widget target; that is where the caret-notification-center image that you used for your button resides. If you have an overlap of image assets between your container app and widget it is a good idea to use a dedicated catalog that only contains the assets that will actually be shared. This will reduce bloat in your finalized bundles by not including images that are unused.
Switch back to MainInterface.storyboard and open the Assistant Editor. Make sure TodayViewController.swift is the active file. Add this at the top of the file:
import CryptoCurrencyKit |
This imports the CryptoCurrencyKit framework.
Next, you need to update the class declaration, like this:
class TodayViewController: CurrencyDataViewController, NCWidgetProviding { |
This makes the TodayViewController
a subclass of CurrencyDataViewController
, and ensures it conforms to the NCWidgetProviding
protocol.
CurrencyDataViewController
is included in CryptoCurrencyKit
and is also used by the primary view within Crypticker. Since the widget and app will be displaying similar information through a UIViewController, it makes sense to put reusable components in a superclass and then sub-class that as requirements vary.
NCWidgetProviding
is a protocol specific to widgets; there are two methods from the protocol that you’ll be implementing.
Ctrl+drag from the button to the class, just below the class declaration. In the popup dialog make sure the Connection is set to Outlet, the Type is set to UIButton
, and enter toggleLineChartButton
for the Name. Click Connect.
Then ctrl+drag from the button to the bottom of the class this time. In the popup dialog change the Connection to Action, set the Type to UIButton
, and enter toggleLineChart
for the Name. Click Connect.
TodayViewController
subclasses CurrencyDataViewController
, which has outlets for the price label, price change label and line chart view. You now need to wire these up. In the Document Outline, ctrl+drag from Today View Controller to the price label (the one with its text set to $592.12). Select priceLabel
from the popup to create the connection. Repeat for the other label, selecting priceChangeLabel
from the popup. Finally, do the same for the Line Chart View, selecting lineChartView
from the popup.
Auto Layout
For your widget to be adaptive, you’ll need to set up Auto Layout constraints. New with iOS 8 is the concept of Adaptive Layout. The general idea is that views are designed with a single layout that can work on a variety of screen sizes. The view is considered adaptive when it can adapt to unknown future device metrics.
One of the constraints that you will add is to show and hide the chart, and help define the overall height of the widget. Notification Center will rely on you to display your widget with the appropriate height.
Select the $592.12 label and then select Editor\Size to Fit Content. If the Size to Fit Content option is disabled in the menu, deselect the label, and then reselect it and try again; sometimes Xcode can be a little temperamental. Next, using the Pin button at the bottom of the storyboard canvas, pin the Top and Leading space to 8 and 16 respectively. Make sure that Constrain to margins is turned off.
Select the +1.23 label and again select Editor\Size to Fit Content. Then, using the Pin button, pin the Top and Trailing space both to 8.
Select the Button, and using the Pin button, pin its Top and Trailing space to 0, and its Bottom space to 8
. Pin both its Width and Height to 44
. Make sure that Constrain to margins is turned off.
You need to reduce the priority for the bottom spacing constraint of the button. Select the button, and then open the Size Inspector. Locate the Bottom Space to: constraint in the list of constraints, click Edit and change it’s Priority to 250
.
By lowering the priority you are allowing the Auto Layout system to break this constraint, as it deems necessary. 250 is an arbitrary value that happens to be less than 1,000, which is what the priority is set to for all constraints by default and means required. This constraint needs to be broken when the widget is in its collapsed state. By having varying levels of priorities per constraint you are hinting to the system which constraints to break first or last when a conflict arises.
Finally, select the Line Chart View. Using the Pin button, pin its Leading, Trailing and Bottom space to 0
and its Height to 98
.
From the Document Outline select the view controllers View, the choose Editor\Resolve Auto Layout Issues\All Views\Update Frames. This will fix any Auto Layout warnings in the canvas by updating the frames of the views to match their constraints. If Update Frames is not enabled then you laid everything out perfect and it is unnecessary to run.
With all of your constraints in place, the final step is to create an outlet for the line chart view’s height constraint. Find the Line Chart View in the Document Outline and click the disclosure triangle.
Then click the disclosure triangle for the Constraints to find the necessary height constraint. Select it, and then ctrl+drag into the Assistant Editor, releasing just below the other outlet. In the popup, make sure the Connection is set to Outlet and enter lineChartHeightConstraint
for the Name. Click Connect.
Implementing TodayViewController.swift
Now the interface is in place and everything is wired up, open up TodayViewController.swift in the Standard Editor.
You’ll notice you’re working with bog-standard UIViewController
subclass. Comforting, right? Although later you’ll encounter a new method called widgetPerformUpdateWithCompletionHandler
from the NCWidgetProviding
protocol. You’ll learn more about it towards the end of the tutorial.
This view controller is responsible for displaying the current price, price difference, responding to a button press and showing the price history in a line chart.
Define a property at the top of TodayViewController
that you’ll use to track if the line chart is visible or not:
var lineChartIsVisible = false |
Now replace the boilerplate viewDidLoad()
method with the following implementation:
override func viewDidLoad() { super.viewDidLoad() lineChartHeightConstraint.constant = 0 lineChartView.delegate = self; lineChartView.dataSource = self; priceLabel.text = "--" priceChangeLabel.text = "--" } |
This method does the following:
- Sets the line view charts’ height constraints’ constant to 0, so that it’s hidden by default.
- Sets self as the data source and delegate for the line chart view.
- Sets some placeholder text on the two labels.
Still in TodayViewController
, add the following method:
override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) fetchPrices { error in if error == nil { self.updatePriceLabel() self.updatePriceChangeLabel() self.updatePriceHistoryLineChart() } } } |
The fetchPrices
method is defined in CurrencyDataViewController
, and is an asynchronous call that takes a completion block. The method makes a request to the web-service mentioned at the beginning of the tutorial to obtain Bitcoin price information.
In the method’s completion block update both labels and the line chart. The update methods are also defined for you in the super-class. They simply take the values retrieved by the fetchPrices
method and format them appropriately for display.
Due to the design of the widget, you’ll also need to implement widgetMarginInsetsForProposedMarginInsets
to provide custom margin insets. Add the following code to TodayViewController
:
func widgetMarginInsetsForProposedMarginInsets (defaultMarginInsets: UIEdgeInsets) -> (UIEdgeInsets) { return UIEdgeInsetsZero } |
By default, widgets have a large left margin, which is evident in many of Apple’s default widgets. If you want to fill the entire width of Notification Center, you must implement this method and return UIEdgeInsetsZero
, which translates to 0
on all sides.
Now it’s time to see what you have so far. Select the BTC Widget scheme. Build and run. Choose Today as the app to run when prompted.
- If Notification Center doesn’t appear, swipe down from the top of the screen to activate it.
- If the widget doesn’t appear in Notification Center, you’ll need to add it via the Edit menu. Towards the bottom of the Today’s view content you will see an Edit button. Tap the button to reveal a menu of all Today Extensions that are installed on the system. Here you can enable, disable and re-order them as desired. Enable BTC Widget if not already.
Cool! Your widget now displays real-time Bitcoin pricing right in Notification Center. But you may have noticed a problem; the button doesn’t work and you can’t see the line chart.
Next, you’ll implement toggleLineChart
for the button that you added so that it expands the widget’s view and exposes the line chart. As the method name implies, this button will behave as a toggle; it will also collapse the view to hide the chart.
Replace the empty toggleLineChart
method with the following code:
@IBAction func toggleLineChart(sender: UIButton) { if lineChartIsVisible { lineChartHeightConstraint.constant = 0 let transform = CGAffineTransformMakeRotation(0) toggleLineChartButton.transform = transform lineChartIsVisible = false } else { lineChartHeightConstraint.constant = 98 let transform = CGAffineTransformMakeRotation(CGFloat(180.0 * M_PI/180.0)) toggleLineChartButton.transform = transform lineChartIsVisible = true } } |
This method manipulates the constant of the Line Chart Views’ height constraint to toggle its display. It also applies a rotation transform to the button so it accurately reflects the visibility of the chart.
After updating the constraint, you must reload the chart’s data so that it redraws based on the new layout.
You’ll do this in viewDidLayoutSubviews
. Add the following to TodayViewController
:
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() updatePriceHistoryLineChart() } |
Make sure the BTC Widget scheme is selected. Build and run. Choose Today as the app to run when prompted.
On the left, you’ll see how the widget appears when the chart is hidden. On the right, you’ll see how it appears when opened. Not too shabby!
A quick update of the line’s color and you’ll have one sharp looking widget. Add the following method to TodayViewController
:
override func lineChartView(lineChartView: JBLineChartView!, colorForLineAtLineIndex lineIndex: UInt) -> UIColor! { return UIColor(red: 0.17, green: 0.49, blue: 0.82, alpha: 1.0) } |
Make sure the correct scheme is still selected. Build and run. Choose Today as the app to run when prompted.
Your last order of business is to add support to your widget to update its view when it’s off-screen, by allowing the system to create a snapshot. The system does this periodically to help your widget stay up to date.
Replace the existing implementation of widgetPerformUpdateWithCompletionHandler
with the following code:
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) { fetchPrices { error in if error == nil { self.updatePriceLabel() self.updatePriceChangeLabel() self.updatePriceHistoryLineChart() completionHandler(.NewData) } else { completionHandler(.NoData) } } } |
This method does the following:
- It fetches the current price data from the web service by calling fetchPrices.
- If there’s no error the interface is updated.
- Finally – and as required by the NCWidgetProviding protocol – the function calls the system-provided completion block with the .NewData enumeration.
- In the event of an error, the completion block is called with the .Failed enumeration. This informs the system that no new data is available and the existing snapshot should be used.
And that wraps up your Today Extension! You can download the final project here.
Where To Go From Here?
The iOS 8 Notification Center is your own personal playground! Widgets have been available on some other mobile operating systems for years, and Apple has finally provided you the ability to create them.
As an enterprising developer, you might want to take another look at your existing apps and think about how you can update them with widgets. Take it a step further and dream up new app ideas that exploit the possibilities of widgets.
If you’d like to learn more about creating other types of iOS 8 App Extensions, check out our book iOS 8 by Tutorials where you can learn about Photo Extensions, Share Extensions, Action Extensions, and more!
We can’t wait to see what you come up with, and hope to have your Today Extensions at the top of our Notification Centers soon!
iOS 8 Today Extension Tutorial is a post from: Ray Wenderlich
The post iOS 8 Today Extension Tutorial appeared first on Ray Wenderlich.