Every good product I’ve ever seen is because a group of people cared deeply about making something wonderful that they and their friends wanted. They wanted to use it themselves.
Steve Jobs
Most people interact with their watches by glancing at the watch to check the time. For the Apple Watch, Apple has extended this traditional form of interaction by providing glances for your watch applications. Instead of just glancing for the time, users can have a quick glance at the state of the various applications. Glances are shown when users swipe up on the watch. The Apple Watch then shows a scrollable list of glances from apps that support them (up to a maximum of 20 glances). Think of glances as snapshots of the various apps on the watch: Instagram may show the most recently shared photo, and Twitter may show the latest trending tweets. Glances provide users with a quick summary of information that may be important to them. If a user wants more details, tapping a glance launches the corresponding watch app.
If your app supports glances, you need to add a Glance scene to your storyboard file. Each user needs to manually turn on the glance of your application through the Apple Watch application on the watch.
In this chapter, you learn how to implement glances for your watch apps.
From a developer’s perspective, a glance is an additional avenue for your app to display a quick summary of information to the user. Imagine an iPhone app that fetches stock prices at regular time intervals. A user who wants to have a quick look at the price of a particular stock can simply swipe up on the watch face and view the most recently fetched price of the stock. This can be done without launching your app. Of course, if the user wants to view more detailed information, he or she can tap the glance to launch the app. One noteworthy feature of glances is that users have no way of interacting with them—there are no controls for users to interact with. They are solely for the purpose of showing information quickly to the users, hence the name.
1. Using Xcode, create a new iOS App with WatchKit App project and name it DisplayingGlances. For this project, uncheck the Include Notification Scene option and check the Include Glance Scene option (see Figure 9.1).
2. In the WatchKit app, select the Interface.storyboard file. You should now see the Glance Interface Controller together with the Interface Controller (see Figure 9.2).
Note
Each watch app can contain at most one Glance Interface Controller.
3. In the Extension project, you see the GlanceController.swift file (see Figure 9.3).
4. The GlanceController.swift file contains the GlanceController
class, which represents the Glance Interface Controller in the Interface.storyboard file:
import WatchKit
import Foundation
class GlanceController: WKInterfaceController {
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about
// to be visible to user.
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible.
super.didDeactivate()
}
}
Observe that the GlanceController
class extends the WKInterfaceController
class, the same as the Interface Controller. The same Interface Controller lifecycle applies to the Glance Controller as well. The only exception is that for a Glance Controller, the initialization takes place very early so that the information can be quickly displayed. Hence, you should try to update your glances in the willActivate
method.
Glances can be customized to display different types of information. However, as with notifications, users are not allowed to have interactions with the glances. This means that controls, like Button and Slider controls, are not allowed.
Note
Remember that glances are supposed to show users information quickly. Hence, you should design your glances to convey as much relevant information as quickly as possible.
1. Select the Glance Interface Controller in the Interface.storyboard file and view its Attributes Inspector window (see Figure 9.4). Observe that it is divided into two sections: Upper and Lower. Both contain the Group control, which allows you to add controls like labels and images.
2. Click the Upper group in the Attributes Inspector window, and you see a popover list (see Figure 9.5). This is a list of predefined templates for some of the common designs for the Upper section. Select the bottom-left item.
Note
If you do not like the predefined templates, you can always add and lay out your own controls in the Glance Interface Controller.
3. Likewise, click the Lower group, and you see another popover list (see Figure 9.6). Select the top-left item.
4. Add a Label control to the Lower group (see Figure 9.7) and set its attributes as follows:
Font: System Italic
Size: 30
Text Color: Yellow
Style: Bold
Horizontal: Center
Vertical: Center
Note
You can only use the system font (San Francisco) for glances and notifications on the Apple Watch; custom fonts are not supported.
5. The Glance Interface Controller now looks like Figure 9.8.
To test the glance, you need to use the Glance scheme that was created in Xcode when you added the WatchKit App target:
1. In Xcode, select the Glance – DisplayingGlances WatchKit App scheme (see Figure 9.9) and then select iPhone 6 + Apple Watch – 38mm.
2. Run the application on the Apple Watch Simulator. You now see the glance on the Apple Watch Simulator (see Figure 9.10).
At this moment, the glance is not doing anything useful. In the next section, you modify the application to display some useful information.
To make the glance display useful information, you must modify the containing iOS application to perform a background fetch. Your iOS app can then perform a network operation even if it is switched to the background.
In the background fetch, the iOS app connects to a Yahoo web service to fetch the prices of two stocks: AAPL (Apple) and MSFT (Microsoft). Once the prices of the two stocks are fetched, the price of AAPL is sent to the Apple Watch using the ApplicationContext
method in the Watch Connectivity Framework (covered in Chapter 7, “Interfacing with iOS Apps”). (For this example, you send the price of only one stock even though you also downloaded the price of MSFT.)
In the watch app, the values received from the iPhone are saved using the
NSUserDefaults
settings.
Whenever the glance is shown, the values of the stocks are retrieved from the
NSUserDefaults
settings and then displayed.
The containing iOS app connects to the Yahoo web service and downloads the stock prices of Apple and Microsoft when the application is in the background.
1. To implement background fetch on the containing iOS app, select the DisplayingGlances target in Xcode and, in the Capabilities tab, check the Background fetch option under the Background Modes section (see Figure 9.11).
2. In the Info.plist file in the iOS project, add the two keys NSAppTransportSecurity
and NSAllowsArbitraryLoads
, as shown in Figure 9.12. These two keys are needed so that you can connect to a web server using http://
instead of https://
(which is the default).
3. Add the following statements in bold to the AppDelegate.swift file:
import UIKit
import WatchConnectivity
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var window: UIWindow?
//---convert from NSDate format to String---
func currentDateToString() -> String {
let formatter: NSDateFormatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss zzz"
return formatter.stringFromDate(NSDate())
}
//---extract the required data from the JSON string---
func parseJSONData(data: NSData) {
do {
let parsedJSONData =
try NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions()) as!
[String:AnyObject]
let query = parsedJSONData["query"] as!
[String:AnyObject]
if let results = query["results"] as?
[String:AnyObject] {
if let quotes = results["quote"] as?
[[String:AnyObject]] {
for stock in quotes {
let symbol = stock["symbol"] as! String
let ask = stock["Ask"] as! String
let lastupdate = currentDateToString()
//---debugging---
print(symbol)
print(ask)
print(lastupdate)
if symbol == "AAPL" {
do {
let applicationContext = [
"symbol" : symbol,
"lastupdate" : lastupdate,
"ask": ask]
try
WCSession.defaultSession().
updateApplicationContext(
applicationContext)
} catch {
print("(error)")
}
}
}
}
}
} catch {
print(error)
}
}
//---performing a background fetch---
func application(application: UIApplication,
performFetchWithCompletionHandler completionHandler:
(UIBackgroundFetchResult) -> Void) {
/*
For http://, you need to add the following keys in
info.plist:
NSAppTransportSecurity
NSAllowsArbitraryLoads - YES
Application Transport Security has blocked a cleartext
HTTP (http://) resource load since it is insecure.
Temporary exceptions can be configured via your app's
Info.plist file.
*/
let urlString =
"http://query.yahooapis.com/v1/public/yql?q=" +
"select%20*%20from%20yahoo.finance.quotes%20" +
"where%20symbol%20in%20(%22AAPL%22%2C%22MSFT%22)" +
"%0A%09%09&env=http%3A%2F%2Fdatatables.org%2" +
"Falltables.env&format=json"
let session = NSURLSession.sharedSession()
session.dataTaskWithURL(NSURL(string:urlString)!,
completionHandler: {
(data, response, error) -> Void in
let httpResp = response as! NSHTTPURLResponse
if error == nil && httpResp.statusCode == 200 {
//---parse the JSON result---
self.parseJSONData(data!)
completionHandler(UIBackgroundFetchResult.NewData)
} else {
completionHandler(UIBackgroundFetchResult.Failed)
}
}).resume()
}
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
return true
}
Note
Remember to change the shared group name to the one that you have used.
You just enabled the following to happen:
You connect to the Yahoo web service to fetch the price of Apple and Microsoft.
The web service returns the result as a JSON string.
You pass the JSON string to the
parseJSONData:
method to extract the relevant data: stock symbol and asking price.
The
currentDateToString
method returns the current date and time as a String
object.
The stock symbols, asking prices, and the date and time the prices were fetched are sent to the Apple Watch using the
ApplicationContext
method.
4. Select the DisplayingGlances scheme (see Figure 9.13) in Xcode and run the application on the iPhone Simulator.
5. To simulate a background fetch on the application, select Debug | Simulate Background Fetch in Xcode. If the stock prices are downloaded correctly, you should now see the prices in the Output window (see Figure 9.14).
Now that the stock prices are downloaded and saved, you can display them on the Glance Interface Controller:
1. In the GlanceController.swift file, create three outlets for the three Label controls in the Glance Interface Controller:
import WatchKit
import Foundation
class GlanceController: WKInterfaceController {
@IBOutlet var lblSymbol: WKInterfaceLabel!
@IBOutlet var lblLastUpdate: WKInterfaceLabel!
@IBOutlet var lblAsk: WKInterfaceLabel!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
2. Add the following statements in bold to the GlanceController.swift file:
import WatchKit
import Foundation
import WatchConnectivity
class GlanceController: WKInterfaceController, WCSessionDelegate {
@IBOutlet var lblSymbol: WKInterfaceLabel!
@IBOutlet var lblLastUpdate: WKInterfaceLabel!
@IBOutlet var lblAsk: WKInterfaceLabel!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
func session(session: WCSession, didReceiveApplicationContext
applicationContext: [String : AnyObject]) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(applicationContext["symbol"]! as! String,
forKey: "symbol")
defaults.setValue(applicationContext["lastupdate"]! as! String,
forKey: "lastupdate")
defaults.setValue(applicationContext["ask"]! as! String,
forKey: "ask")
defaults.synchronize()
}
//---convert from string to NSDate---
func dateStringToDate(date:String) -> NSDate {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss zzz"
return dateFormatter.dateFromString(date)!
}
override func willActivate() {
// This method is called when watch view controller is about to
// be visible to user.
super.willActivate()
let defaults = NSUserDefaults.standardUserDefaults()
if defaults.valueForKey("lastupdate") != nil {
//---retrieve the price and date fetched from the settings---
let lastupdate = dateStringToDate(
(defaults.valueForKey("lastupdate") as? String)!)
let price = defaults.valueForKey("ask") as? String
let symbol = defaults.valueForKey("symbol") as? String
//---the difference between the current time and the time the
// price was fetched---
let elapsedTime = NSDate().timeIntervalSinceDate(lastupdate)
//---convert to seconds---
let elapsedTimeSeconds = Int(elapsedTime)
//---convert the time to mins and secs---
let elapsedMin = elapsedTimeSeconds / 60
let elapsedSec = elapsedTimeSeconds % 60
if elapsedMin > 0 {
lblLastUpdate.setText(
"(elapsedMin) mins (Int(elapsedSec)) secs")
} else {
lblLastUpdate.setText("(Int(elapsedTime)) secs")
}
//---show the info on the glance---
self.lblSymbol.setText(symbol)
self.lblAsk.setText("$" + price!)
}
}
You just enabled the following to happen:
When the Glance Controller is awakened, you want to activate the session for the Watch Connectivity Framework so that you can receive the incoming stock information sent from the watch.
You load the values saved in the
NSUserDefaults
settings. For simplicity, you are retrieving only one stock price: AAPL. The value of the AAPL key is a dictionary containing the price as well as the date and time it was fetched.
The
dateStringToDate:
method accepts the date and time as a String
and then returns an NSDate
object.
You calculate the elapsed time since the price was fetched using the
timeIntervalSinceDate:
method of the NSDate
object.
You then display the stock symbol, the elapsed time since the price was fetched, and the price of the stock in the Glance Interface Controller.
3. In Xcode, switch to the Glance – DisplayingGlances WatchKit App scheme and test the application on the iPhone Simulator. The Glance Interface Controller should now look like Figure 9.15.
In this chapter, you learned how to implement glances in your Apple Watch application. You also learned how to perform background fetch in your containing iOS application and then display the information downloaded in your Glance Interface Controller.
3.138.34.31