watchOS 2 Tutorial Part 3: Animation

Mic Pringle
Note: This is a brand new tutorial released as part of the iOS 9 Feast. Enjoy!

Welcome back to our watchOS 2 tutorial series!

In the first part of this series, you learned about the basics of watchOS 2 development by creating your first interface controller.

In this second part of the series, you learned how to add tables to your app.

In this third part of the series, you’ll learn how to use watchOS 2 animation by adding a new check-in interface into your app.

In the process, you’ll learn:

  • How to create image-based animations;
  • How to use the new watchOS 2 animation API.

And with that, let’s get cracking! ┗(°0°)┛

Note: This tutorial picks up where we left things off in the previous tutorial. You can either continue with the same project, or download it here if you don’t have it already.

Getting Started

Open Watch\Interface.storyboard and drag an Interface Controller from the Object Library onto the storyboard canvas. With the interface controller selected, open the Attributes Inspector and set Identifier to CheckIn. You do this so you can present the interface controller from within ScheduleInterfaceController.

Next, drag a Group from onto the new interface controller from the Object Library. Use the Attributes Inspector to make the following changes:

  • Set Layout to Vertical;
  • Set Mode to Center;
  • Set the Horizontal alignment to Center;
  • Set Height to Relative to Container.

Your interface controller should now look like this:

Background-Group

Drag another Group into the existing group, and make the following changes using the Attributes Inspector:

  • Set Spacing to 4;
  • Set the Horizontal alignment to Center;
  • Set Width to Size To Fit Content;
  • Set Height to Fixed, with a value of 30.

Add a Label, an Image, and then another Label to this new layout group. The two labels will display the origin and destination of each flight, respectively.

Select the image, either in the storyboard or in the Document Outline. Using the Attributes Inspector, make the following changes:

  • Set Image to Plane;
  • Set Tint to #FA114F;
  • Set Vertical alignment to Center;
  • Set Width to Fixed, with a value of 24;
  • Set Height to Fixed, with a value of 20.

As the image is black it might be difficult to see against the black background of the interface controller. You’ll just have to trust me that it’s there.

Select the left label and set its Text to MAN. Also change its Font to System, with a style of Semibold and a size of 20.0. Finally set its Vertical alignment to Center.

Make the same changes to the right label, except using the text SFO. Your interface controller should now look like the following:

Upper-Group-Complete

Now it’s time to add that huge check-in button!

Adding the Check-In Button

Drag a Button from the Object Library onto the interface controller, making sure it’s positioned as a sibling of the group containing the origin and destination labels:

Button-Position

Buttons in WatchKit are incredibly flexible; you can use them with their stock appearance – how the one you just added looks – or you can turn them into a layout group and add other interface objects to customize their appearance. That’s exactly what you’re going to do here.

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

  • Set Content to Group;
  • Set the Horizontal alignment to Center;
  • Set the Vertical alignment to Center.

Your interface controller should now look like this:

Button-Group

You may have noticed that when you changed the Content attribute of the button, a new group appeared in the Document Outline:

Button-Embedded-Group

This is what you’re going to use as the background of your custom check-in button. Select this group and make the following changes using the Attributes Inspector:

  • Set Color to #FA114F;
  • Set Radius to 39;
  • Set Width to Fixed, with a value of 78;
  • Set Height to Fixed, with a value of 78.

The interface controller should now look like this:

Round-Button

Your check-in button is really starting to take shape. The only thing missing is the label, so you’ll add that now.

Drag a Label from the Object Library into the group belonging to the button, and then select it. Once again, make the following changes using the Attributes Inspector:

  • Set Text to Check In;
  • Set Font to System, with a style of Semibold and a size of 16.0;
  • Set the Horizontal alignment to Center;
  • Set the Vertical alignment to Center.

Your finished check-in interface controller should now look like this:

Check-In-Interface-Complete

With the interface complete, it’s now time to create a subclass of WKInterfaceController to manage this controller, and to update ScheduleInterfaceController to show it.

Creating the Controller

Right-click on the Watch Extension group in the Project Navigator and choose New File…. In the dialog that appears select watchOS\Source\WatchKit Class and click Next. Name the new class CheckInInterfaceController, and make sure it’s subclassing WKInterfaceController and that Language is set to Swift:

File-Options

Click Next, and then Create.

When the new file opens in the code editor, delete the three empty method stubs so you’re left with just the import statements and the class definition.

Add the following at the top of the class:

@IBOutlet var backgroundGroup: WKInterfaceGroup!
@IBOutlet var originLabel: WKInterfaceLabel!
@IBOutlet var destinationLabel: WKInterfaceLabel!

Here you’re simply adding outlets for the outer-most group and the two labels of the interface you just created. You’ll hook everything up momentarily.

Next, add the following just below the outlets:

var flight: Flight? {
  didSet {
    if let flight = flight {
      originLabel.setText(flight.origin)
      destinationLabel.setText(flight.destination)
    }
  }
}

You know the score by now! Here you’ve added an optional property of type Flight, which includes a property observer. When the observer is fired, you try to unwrap flight, and if that succeeds you use flight to configure the two labels. This is all familiar territory now, right?

Now you just need to set flight when the controller is presented. Add the following to CheckInInterfaceController:

override func awakeWithContext(context: AnyObject?) {
  super.awakeWithContext(context)
  if let flight = context as? Flight { self.flight = flight }
}

Again, this should be super familiar by now. You try to unwrap and cast context to an instance of Flight, and if that succeeds you use it to set self.flight, which in-turn triggers the property observer and configures the interface.

Finally, add the following action just below awakeWithContext(_:):

@IBAction func checkInButtonTapped() {
  // 1
  let duration = 0.35
  let delay = dispatch_time(DISPATCH_TIME_NOW, Int64((duration + 0.15) * Double(NSEC_PER_SEC)))
  // 2
  backgroundGroup.setBackgroundImageNamed("Progress")
  // 3
  backgroundGroup.startAnimatingWithImagesInRange(NSRange(location: 0, length: 10), duration: duration, repeatCount: 1)
  // 4
  dispatch_after(delay, dispatch_get_main_queue()) { () -> Void in
    // 5
    self.flight?.checkedIn = true
    self.dismissController()
  }
}

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

  1. You create two constants: one for the duration of the animation, and one for the delay after which the controller will be dismissed. Instead of being a Double, delay is an instance of dispatch_time_t since you’ll be using it with Grand Central Dispatch.
  2. You load a sequence of images named Progress and set them as the background image of backgroundGroup. Remember that layout groups conform to WKImageAnimatable, which allows you to use them to play back animated image sequences.
  3. You begin playback of the image sequence. The range you supply covers the entire sequence, and a repeat count of 1 means the animation will play just once.
  4. You use Grand Central Dispatch to execute the closure after the given delay.
  5. In the closure, you mark flight as checked-in, and then dismiss the controller.

Now you just need to add the images to the project, and hook up the outlets and single action.

Download this zip file, unzip the file, and drag the folder into your Watch\Assets.xcassets.

Make sure you drag the folder and not it’s contents. This should create a new group in the asset catalog called Progress, containing several image sets:

Progress-Image-Group

With the images sorted, it’s time to sort the outlets and button action.

Open Watch\Interface.storyboard and select your new interface controller. In the Identity Inspector, change Custom Class\Class to CheckInInterfaceController:

Custom-Class

Right-click on CheckIn in the Document Outline to invoke the outlets and actions popup. Then, connect backgroundGroup to outer-most group in the interface controller:

Background-Group-Outlet

Connect destinationLabel to the label containing SFO, and connect originLabel to the label containing MAN.

Next, connect checkInButtonTapped to the big, round, pink button:

Connect-Action

The final change you need to make before you can build and run is to actually present this interface controller.

Presenting the Controller

Open ScheduleInterfaceController.swift, find table(_:didSelectRowAtIndex:), and replace its contents with the following:

let flight = flights[rowIndex]
let controllers = ["Flight", "CheckIn"]
presentControllerWithNames(controllers, contexts:[flight, flight])

Here you retrieve the appropriate flight from flights using rowIndex, create an array containing the identifiers of the two interface controllers you want to present, and then present them, passing flight as the context to both.

Build and run. Tap a flight and you’ll see a pair of interface controllers are presented. Swipe left to reveal the check-in controller, and then tap the button to trigger the animation and check-in:

Image-Animation

This looks great as-is, but it’d be even better if checked-in flights were highlighted on the schedule interface controller as well. You’ll address that in the next and final section.

Highlighting the Flight

Open FlightRowController.swift and add the following method to it:

func updateForCheckIn() {
  let color = UIColor(red: 90/255, green: 200/255, blue: 250/255, alpha: 1)
  planeImage.setTintColor(color)
  separator.setColor(color)
}

Here you’re creating an instance of UIColor, and then using it to set the tint color and color of planeImage and separator respectively. This method will be called from within an animation closure, so the color change will be nicely animated.

Next, open ScheduleInterfaceController.swift and add the following property to the top of the class:

var selectedIndex = 0

You’ll use this to remember which table row was selected when presenting the two interface controllers. Now you just need to set it when a table row is selected. Add the following just above the call to presentControllerWithNames(_:contexts) in table(_:didSelectRowAtIndex:):

selectedIndex = rowIndex

This sets selectedIndex to the index of the selected table row.

Finally, add the following to ScheduleInterfaceController, just below awakeWithContext(_:):

override func didAppear() {
  super.didAppear()
  // 1
  if flights[selectedIndex].checkedIn, let controller = flightsTable.rowControllerAtIndex(selectedIndex) as? FlightRowController {
    // 2
    animateWithDuration(0.35, animations: { () -> Void in
      // 3
      controller.updateForCheckIn()
    })
  }
}

Here’s what’s happening in the code above:

  1. You check to see if the selected flight is checked-in, and if so you try and cast the row controller at the corresponding index in the table to an instance of FlightRowController.
  2. If that succeeds, you use the new animation API on WKInterfaceController to execute the given closure, over a duration of 0.35 seconds.
  3. In the closure, you call the method you just added to FlightRowController, which changes the color of the plane image and separator of that table row, and provides the user with some visual feedback that they’re now checked-in.

Build and run. Follow the same steps as before to check in for a flight, and you’ll see that when you’re returned to the schedule interface controller, the colors of the plane image and separator on the corresponding table row crossfade to a new color:

Crossfade-Animation

Congratulations! You’ve now finished implementing your very first set of WatchKit animations.

Where to Go From Here?

Here is the finished example project from this tutorial series so far.

In this tutorial you’ve learnt how to create two different kinds of WatchKit animation. The first, using an animated sequence of images, and the second, using the new animation API on WKInterfaceController. You’re now suitably primed to add plenty of visual flair to your own watchOS 2 apps!

So, where to next? The fourth and final part of this session, of course! Where you’ll learn all about Watch Connectivity, a brand new framework in watchOS 2 and iOS 9.

You might also be interested in our book watchOS 2 by Tutorials that goes into much greater detail about making watchOS 2 apps – from beginning to advanced.

If you have any questions or comments on this tutorial, please join the forum discussion below! :]

Mic Pringle

Mic Pringle is a mobile application developer by trade, but has recently found himself dabbling in Ruby, PHP, and JavaScript.

When not knee-deep in code, Mic enjoys spending time with his wife Lucy and their daughter Evie, watching his beloved Fulham football club, and weather permitting, tearing up the English countryside on his Harley Davidson.

Other Items of Interest

Black Friday Sale

Starts in…

0
:
0
:
0

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

iOS Team

... 78 total!

Android Team

... 27 total!

Unity Team

... 12 total!

Articles Team

... 15 total!

Resident Authors Team

... 20 total!

Podcast Team

... 7 total!

Recruitment Team

... 9 total!