Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4400

WatchKit Tutorial with Swift: More Tables, Glances and Handoff

$
0
0
Thank you for being a part of the Swift Spring Fling!

Continue your WatchKit adventures during the Swift Spring Fling!

Note from Ray: This is a bonus WatchKit tutorial released as part of the Spring Swift Fling. If you like what you see, be sure to check out WatchKit by Tutorials for even more on developing for the Apple Watch. We hope you enjoy! :]

Welcome to the second and final part of the Swift Spring Fling WatchKit tutorial series! CoinTracker, your cryptocurrency-tracking watch app, is ready to be souped-up and taken to the next level.

In Part 1, you created the initial interface controller, added a table to display the available cryptocurrencies and their current prices, and delegated the network requests to the containing iOS app.
F has
Your task in this tutorial is to take CoinTracker to its final form. Specifically, you’ll do the following:

  • Flesh out the second interface controller you added at the very end of Part 1 to show the details of the selected cryptocurrency.
  • Add a Glance to your project to display the current price of the user’s favorite cryptocurrency.
  • Pass contextual information to the Watch app using Handoff; this lets the Watch app update its interface and provide a richer experience to the user.

Here’s a sneak peek at what you’ll be building throughout the rest of this tutorial:

Tease

Does that whet your appetite? Of course it does — time to get right to it!

The Devil is in the Detail…Controller

Open the Xcode project you completed in Part 1, or alternatively, you can download the final completed project from Part 1 and start with that.

You finished off Part 1 of this tutorial by adding a second interface controller to display the details of the selected coin. It’s now time to flesh out that interface controller.

Open Interface.storyboard from the CoinTracker WatchKit App group and select the empty details interface controller. Open the Attributes inspector and set the Identifier to CoinDetailInterfaceController so you can access this interface controller in your code.

Now, on to the interface itself. Drag a Group from the Object Library onto the empty interface controller.

Next, drag an Image into the group. Select the image, and use the Attributes Inspector to change Position\Vertical to Center. Then set both Size\Width and Size\Height to Fixed with a value of 40 for each:

02

Now drag a Label into the group. You’ll notice it’s automatically placed to the right of the image – this is because the group’s Layout attribute is set to Horizontal by default — and exactly what you need for your interface.

Select the label, and use the Attributes Inspector to set the following values:

  • Text to Coin
  • Font to System, with a Style of Bold and Size of 34
  • Horizontal position to Right
  • Vertical position to Center

Select the group again; you can click on the space between the image and the label, or select it from the Document Outline if that’s easier. Use the Attributes Inspector to change Group\Insets to Custom and set Top, Bottom, Left and Right all to 2, like so:

01

That create a little space around the image and label so they aren’t hard up up against the edges of the screen.

Once you’ve done that, your interface controller should look like the following:

03

The bones are there; now it’s just a matter of filling in some real data.

Setting up Outlets and Data

The image and label you’ve just added will contain the icon and name of the selected currency, respectively.

Open CoinDetailInterfaceController.swift from the CoinTracker WatchKit Extension group and add the following two outlets just below the coin property:

@IBOutlet weak var coinImage: WKInterfaceImage!
@IBOutlet weak var nameLabel: WKInterfaceLabel!

You’ll use these outlets to populate the image and label.

Next, add the following code just inside the if let block in awakeWithContext(_:):

coinImage.setImageNamed(coin.name)
nameLabel.setText(coin.name)

Here you use coin.name as both the image name and the text of the label. That trick works because the images already provided in the asset catalog of the CoinTracker WatchKit App group have exactly the same names as their corresponding currency. Hey, anything to save a bit of coding time, right? :]

Head back to Interface.storyboard and Right-click (or Control-click) on the yellow interface controller icon on CoinDetailInterfaceController to invoke the connections dialog. Drag from coinImage to the image to connect the two as shown below:

04

Now drag from nameLabel to the label to connect them as well.

Ensure the CoinTracker WatchKit App scheme is selected, then build and run your app; select any coin from the list and you’ll see the icon and name appear on the details screen like so:

05

Okay — you now have the icon and name displayed, which is great. However, you’re a savvy cryptocurrency trader and you want the current financial data as well.

Financial data? Yes please.

Financial data? Yes please.

Fortunately, that’s an easy task with the table functionality of WatchKit — something you’ve already seen in the previous tutorial.

A Table For Three, Please

The three key pieces of cryptocurrency financial data to show on the screen are as follows:

  1. The current price
  2. The price from 24 hours ago
  3. The last 24-hour trading volume

This bring the functionality of the Watch app directly in line with the containing iOS app. Feature-parity FTW! :]

Now, you could add several labels to display this information, and lay them out manually to get their positions just right. But this approach doesn’t plan for the future; your data provider could add daily high and low prices or moving averages, and managing all those labels could quickly become unwieldy.

Too! Many! Labels!

Too! Many! Labels!

Luckily, there is a better way. You can use a table to contain the data and reuse the same row controller you created in Part 1 – how’s that for code reuse?

Open Interface.storyboard and drag a Table from the Object Library onto CoinDetailInterfaceController, making sure to position it just below the group containing the image and label:

06

Select Table Row Controller in the Document Outline, and then use the Identity Inspector to set Custom Class\Class to CoinRow. In the Attributes Inspector, set the Identifier to CoinRow.

Next, select the group inside the table row and use the Attributes Inspector to set Group\Layout to Vertical; you’ll want the contents of this group to be laid out underneath each other, not side-by-side.

Drag two Labels from the Object Library into the table row; you’ll notice the bottom label is slightly cut off. To fix this, select the group containing the two labels and change the Size\Height in the Attributes Inspector to Size To Fit Content. The group will expand to properly house both labels.

Select the top label in the group and use the Attributes Inspector to set Text to Title, Position\Horizontal to Right, and Font to Headline; this last step makes the title a little more pronounced than the rest of the text on the screen.

Now, select the bottom label and set its Text to Detail and Position\Horizontal to Right.

Your completed table row should now look like the following:

07

The final step to setting up your table row is to connect the outlets already defined in CoinRow to the labels you’ve just created.

In the Document Outline, Right-click (or Control-click) on CoinRow to invoke the connections dialog. Connect both titleLabel and detailLabel to their corresponding labels in the table row like so:

08

Now open CoinDetailInterfaceController.swift and add the following outlet just below the existing ones:

@IBOutlet weak var table: WKInterfaceTable!

Once this outlet is connected, you’ll be able to use it to populate the table with the correct number of rows.

Next, add the following code just below the point where you set the icon and name inside awakeWithContext(_:):

// 1
let titles = ["Current Price", "Yesterday's Price", "Volume"]
let values = ["\(coin.price) USD", "\(coin.price24h) USD", String(format: "%.4f", coin.volume)]
 
// 2
table.setNumberOfRows(titles.count, withRowType: "CoinRow")
 
// 3
for i in 0..<titles.count {
  if let row = table.rowControllerAtIndex(i) as? CoinRow {
    row.titleLabel.setText(titles[i])
    row.detailLabel.setText(values[i])
  }
}

Here’s the play-by-play of what’s happening:

  1. You first create two arrays: the first holds the titles of each row, and the second the values.
  2. Next, you set the number of rows on the table; in this case you use the count of the titles array. You also inform the table that it should be using the CoinRow row type, which matches the identifier you set in the storyboard.
  3. Finally, you iterate over each row in the table and set its titleLabel and detailLabel to the corresponding values in the titles and values arrays. You also make use of downcasting to ensure you’re dealing with an instance of CoinRow.

All that’s left to do now is connect the table outlet to the table in the storyboard.

Jump back to Interface.storyboard and Right-click (or Control-click) on the yellow interface controller icon on CoinDetailInterfaceController to invoke the connections dialog. Drag from the table outlet to the table to connect the two:

09

Make sure the CoinTracker WatchKit App scheme is selected and build and run your app. Select any coin from the list and you’ll see all the financial data displayed in the table:

10

Sweet! The Watch app now has feature-parity with the iOS app, and with a tap of the wrist you can check the price, previous price, and 24-hour trading volume of your favorite cryptocurrencies.

You could call it a day — or you could take advantage of some of the unique features the Apple Watch and WatchKit have to offer, such as glances and Handoff. What say you, good sir?

ChallengeAccepted

I thought you’d be interested! :]

Setting Your Favorite Cryptocurrency

A glance is a cracking way to provide your users with useful, timely read-only information without having to first navigate their way through your Watch app. As a completely random example, you could, perhaps, display the current price of their favorite cryptocurrency! :]

But before you add a glance to your app, you need to give your users a way to inform the Watch app of their favorite currency.

Open Interface.storyboard and drag a Switch from the Object Library onto CoinDetailInterfaceController, making sure to position it just below the table. Select the switch and use the Attributes inspector to set Title to Favorite and Font to Caption 2. As well, ensure its State is set to Off:

11

Open the Assistant Editor and make sure CoinDetailInterfaceController.swift is displayed. Control-drag from the switch into the class just below the existing outlets to create a new one. Name it favoriteSwitch:

12

Repeat the process, but this time drag to just below awakeForContext(_:) and change Connection to Action. Name it favoriteSwitchValueChanged, and once the action is created close the Assistant Editor.

Open CoinDetailInterfaceController.swift and add the following constants to the top of the class, just below the coin property:

let defaults = NSUserDefaults.standardUserDefaults()
let favoriteCoinKey = "favoriteCoin"

Here you create a reference to the standard user defaults along with a constant that will act as the key used to store and retrieve the users favorite cryptocurrency from the defaults.

Next, add the following code to favoriteSwitchValueChanged(_:):

// 1
if let coin = coin {
  // 2
  defaults.removeObjectForKey(favoriteCoinKey)
  // 3
  if value {
    defaults.setObject(coin.name, forKey: favoriteCoinKey)
  }
  // 4
  defaults.synchronize()
}

Here’s what’s going on in the code above:

  1. First, since the coin property is an optional, you unwrap it to make sure it’s not nil before continuing.
  2. Next, you remove any previously stored favorite from the defaults.
  3. Then, if value is true it means the user has set the switch to on, so you store the name of the current coin as the favorite in the defaults.
  4. Finally, you synchronize the defaults to guarantee the changes are written to disk and any observers are notified of the changes.

The final piece of the puzzle is to make sure the correct state is set on the switch when the interface controller is first displayed. Add the following to awakeWithContext(_:), just below the for loop:

if let favoriteCoin = defaults.stringForKey(favoriteCoinKey) {
  favoriteSwitch.setOn(favoriteCoin == coin.name)
}

Here you first check the defaults to see if a string exists for the favorite key; if so, you compare the string with the name of the current coin and set the state of the switch accordingly.

Make sure the CoinTracker WatchKit App scheme is selected and then build and run your app. Select a currency from the list and then tap the switch to add it as your favorite:

13

Return to the list and select a different currency (not your favorite); the switch should display the off state. Return to the list once again and this time select the coin you favorited; the switch again displays the on state.

That takes care of setting the user’s favorite currency; now it’s just a matter of adding the glance.

Creating a Glance

A glance is made up of three individual parts: the interface, which you create in Interface Builder; a subclass of WKInterfaceController, which you use to populate the glance with the relevant information; and finally a custom build scheme so you can run the glance in the simulator. You’ll tackle each of these three things in order.

Building the Glance Interface

Open Interface.storyboard and drag a Glance Interface Controller from the Object Library onto the storyboard. You’ll notice immediately that two groups already exist in the interface. This is because glances are template-based, and are split into an Upper and Lower group:

14

You can choose a template for each of the two groups independently using the Attributes Inspector — but for now, you’ll just stick with the default templates.

Just as you did with the coin detail interface, drag an Image and a Label into the upper group. These will display the coin icon and name respectively.

Select the image and use the Attributes Inspector to make the following changes:

  • Set Position\Vertical to Center
  • Set Size\Width to Fixed with a value of 40
  • Set Size\Height to Fixed with a value of 40

Next, select the label and update the following attributes in the Attributes Inspector:

  • Set Text to Coin
  • Set Font to System with a Style of Bold and a Size of 34
  • Set Min Scale to 0.7
  • Set Position\Vertical to Center

The Min Scale attribute lets the font shrink to a percentage of the specified size for text that would normally be wrapped or truncated, which is perfect for cryptocurrencies with long names. The entire interface for a glance must fit within a single screen, since glances are non-interactive and don’t support scrolling. Adjusting the font is a great alternative to wrapping text in situations where space is at a premium — and on the Apple Watch, every pixel counts! :]

Your glance interface controller should now look like the following:

15

Time to move on to the lower group.

Drag a new Group from the Object Library into the lower group and change its Layout to Vertical. Also set both Position\Horizontal and Position\Vertical to Center. Finally, set Size\Width to Size To Fit Content.

Drag two Labels into this new group. Select the first label and use the Attribute Inspector to update the label’s attributes as follows:

  • Set Text to 0.00
  • Set Font to System with a Style of Bold and a Size of 38
  • Set Min Scale to 0.5
  • Set Position\Horizontal to Center

Again, you make use of the Min Scale attribute to make sure any cryptocurrency with a value extending to many decimal places still fits on a single line. Remember that within a glance, screen real-estate is at a premium since the content can’t be scrolled.

Select the second label and make the following changes in the Attribute Inspector:

  • Set Text to USD
  • Set Position\Horizontal to Right
  • Set Position\Vertical to Bottom

This label is a static indicator of the native currency and is positioned underneath and to the right of the price itself.

The completed glance should now looking like the following:

16

Note that the the price is centered and the “USD” text is right-aligned to the price, rather than to the edge of the screen. That’s the power of putting two labels inside a size-to-fit group, that itself lives within another group: everything just flows without manually arranging all the various pieces of the interface.

Now that the glance interface is completed, it’s time to display the cryptocurrency data to the user.

Creating the Glance Interface Controller

Just like every other interface controller in WatchKit, with the exception of notifications, glances are backed by a subclass of WKInterfaceController. You’ll create one now.

Right-click (or Control-click) on the CoinTracker WatchKit Extension group and click New File. Select the iOS\Source\Swift File template and name the new file CoinGlanceInterfaceController. Click Create.

Open CoinGlanceInterfaceController.swift and replace its contents with the following:

import WatchKit
import CoinKit
 
class CoinGlanceInterfaceController: WKInterfaceController {
 
  // 1
  @IBOutlet weak var coinImage: WKInterfaceImage!
  @IBOutlet weak var nameLabel: WKInterfaceLabel!
  @IBOutlet weak var priceLabel: WKInterfaceLabel!
 
  // 2
  let helper = CoinHelper()
  let defaults = NSUserDefaults.standardUserDefaults()
 
  override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
 
    // 3
    if let favoriteCoin = defaults.stringForKey("favoriteCoin") ?? "DOGE" {
      // 4
      let coins = helper.cachedPrices()
      for coin in coins {
        // 5
        if coin.name == favoriteCoin {
          coinImage.setImageNamed(coin.name)
          nameLabel.setText(coin.name)
          priceLabel.setText("\(coin.price)")
          break
        }
      }
    }
  }
}

Taking each commented section in turn:

  1. You define three outlets for the coin icon, name, and current price.
  2. You create a reference to the CoinHelper class so you can access the cached coin data, and a reference to the standard user defaults so you can retrieve the user’s favorite cryptocurrency.
  3. You then ask the defaults for the user’s favorite currency. It’s possible the user hasn’t selected a favorite and the defaults will return nil. To cover this case, you use the nil coalescing operator to select Dogecoin by default. Everyone loves the Doge, after all. ;]
  4. You next retrieve the cached coin data using the CoinHelper class and iterate over each of the cached coins.
  5. Finally, if the name of the current cached coin matches the favorite, you use its data to populate the glance, via the outlets. You also break out of the loop early since you’ve found a match.

With that in place you now just need to connect everything up.

Open Interface.storyboard and select the Glance Interface Controller. In the Identity Inspector set Custom Class\Class to CoinGlanceInterfaceController. Then, right-click on the yellow interface controller icon to invoke the connections dialog and connect the three outlets to their corresponding interface elements like so:

17

Before you build and run, there’s just one more thing you need to do before you can run the glance in the simulator — you need to create a custom build scheme.

Creating a Glance Build Scheme

When you first add a WatchKit App target to your project, Xcode adds a matching build scheme so you can run your app in the simulator. But you’ll need to create schemes manually for glances and notifications you add to the storyboard yourself.

From the Xcode menu, choose Product\Scheme\Manage Schemes…. In the dialog that appears select the CoinTracker WatchKit App scheme, click the gear icon, and choose Duplicate:

18

Name the new scheme CoinTracker Glance, and then select the Run step in the source list on the left hand side. In the main pane, change Watch Interface to Glance like so:

19

Click Close, and then Close again to save your changes.

With that done, it’s finally time to test your glance. Make sure the new CoinTracker Glance scheme is selected, then build and run. You should see your glance appear in the simulator, populated with the data of either your favorited cryptocurrency, or everyone’s favorite default currency, Dogecoin:

20

Tap the glance, and you’ll be thrown straight into the Watch app and greeted by the list of available currencies. But wouldn’t it be even better if you could somehow jump directly to the details screen for the currency displayed on the glance? Of course it would — and you can do exactly that with Handoff.

Adding Handoff to the Glance

Handoff is one of the stand-out features of WatchKit; it lets you pass contextual information from the glance to the Watch app, even though they run as separate processes. For example, you could pass the name of the cryptocurrency being displayed by the glance to the Watch app so it can load the details of that currency on launch. As a matter of fact, that’s exactly what you’re going to do! :]

One of selling features of Handoff is its simplicity; there’s one method call to pass the contextual information and one overridden method to receive it. That’s all you need to get your glance and Watch app talking to each other — although the conversation is a little one-sided.

Open CoinGlanceInterfaceController.swift and add the following just below where you set the text on priceLabel in awakeWithContext(_:):

updateUserActivity("com.razeware.CoinTracker.glance", userInfo: ["coin": coin.name], webpageURL: nil)

Here you inform WatchKit that there’s a user activity going on. In this case, the user is glancing at a certain currency.

The first parameter is the activity type, which is just a string that identifies the activity using reverse domain notation. You also pass a userInfo dictionary to the Watch app when it’s launched via tapping the glance. The third parameter, webpageURL, is used for watch-to-iPhone Handoff. As of this writing it’s not available and isn’t relevant in this context, so you simply pass nil.

Now, open CoinsInterfaceController.swift and add the following method to the class:

override func handleUserActivity(userInfo: [NSObject : AnyObject]!) {
  // 1
  if let handedCoin = userInfo["coin"] as? String {
    // 2
    let coins = coinHelper.cachedPrices()
    // 3
    for coin in coins {
      if coin.name == handedCoin {
        // 4
        pushControllerWithName("CoinDetailInterfaceController", context: coin)
        break
      }
    }
  }
}

Here’s the breakdown of the above code:

  1. You first check to see if the coin key and value pair exist in the userInfo dictionary. You also try to downcast the value to a String as that’s what you originally passed from the glance.
  2. If the key and value pair exist, you retrieve all the cached coins using the CoinHelper class.
  3. Then you enumerate over the cached coin data, looking for a match against the string that came in from the userInfo dictionary.
  4. If there’s a match, you push the coin detail interface controller onto the navigation stack, passing the current coin as the context object.

handleUserActivity(_:) is called only on the initial interface controller — as denoted by the Main arrow in the storyboard shown below — which is why you’ve overridden it in CoinsInterfaceController:

21

With the CoinTracker Glance scheme selected, build and run. Tap the glance once it shows up; the Watch app will launch and you’ll be taken straight to the coin detail interface for the coin displayed by the glance:

22

And there you have it — a complete Watch app that has feature-parity with its companion iOS app, looks great, is designed with future updates in mind, uses Handoff and glances. Give yourself a little pat on the back — you’ve learned a lot in this series! :]

Where to Go From Here?

You can download the final CoinTracker project here, complete with all the code and interface work from this tutorial.

You’ve come a long way in this two-part tutorial series. You’ve touched on a lot of what WatchKit offers: interface controllers, tables, segues, context passing, glances, and Handoff. To top it all off, you learned how to pull it all together into a fully functional, real-world app.

But WatchKit doesn’t end there — no, no, no! There is far more to learn than what we’ve shown you in these two tutorials – we’ve only just scratched the surface.

A good place to start is Apple’s Apple Watch Programming Guide, which offers a great introduction to WatchKit and some concepts that might feel a little unfamiliar if you’re approaching the watch from a background in iOS or OS X.

Next on your reading list should be the Apple Watch Human Interface Guidelines. As the watch is a brand new platform full of new paradigms and design patterns, it’s essential that you pay close attention to these; they’re packed full of advice and recommendations on all aspects of designing interfaces for the watch.

And of course, I highly recommend checking out the 17-part video tutorial series on WatchKit that we have here on the site, as well as picking up a copy of WatchKit by Tutorials.

Do you have any thoughts on tables, glances, or Handoff, or anything else WatchKit-related? Share your thoughts in the forum discussion below!

WatchKit Tutorial with Swift: More Tables, Glances and Handoff is a post from: Ray Wenderlich

The post WatchKit Tutorial with Swift: More Tables, Glances and Handoff appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4400

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>