In this Apple Watch development tutorial series we are building a watchOS 2 app with a companion iPhone app. Together these apps will allow the user to select a date that they started with our company and view how long remains until their 3 month sabbatical. Along the way we are learning about WatchKit, ClockKit, NSDate formatting and complications. In part 1 of this tutorial series we successfully created our watch app with a hard-coded start date.
If you haven’t been with us from the beginning of this Apple Watch development tutorial, the finished code from tutorial 1 is what we’re starting from.
Create iPhone Interface
First we will rename the default ViewController file to “DisplayViewController in Xcode’s Project Explorer. Also rename the class within that file:
The final step in renaming this ViewController is to open the Main.storyboard file, click on the view and change the Custom Class dropdown to DisplayViewController. It took a bit of work, but renaming the ViewController will help distinguish this ViewController from another that we will add in a minute.
Now in the Main.storyboard file, embed the existing view in a navigation controller by selecting Editor >> Embed In >> Navigation Controller
Add the following elements to the view:
- A Bar Button Item in the in the navigation bar that was added to your view when you embedded it into the navigation controller. Change the button’s Text property to “Edit”.
- A label near the top of the view reading “My start date”
- A label below the first reading “Not set”
Align these elements as you wish.
Add a second View Controller to the storyboard with the following elements:
- A navigation bar at the top
- A bar button item in the navigation bar. Highlight this button and select “Save” as the system item type.
- A date picker. Highlight the date picker and select “Date” mode in the Attributes Inspector.
Now “control-drag” from the Edit button on the first view into the second view and select the action type “Present Modally”.
Your storyboard should like like this:
Run your project and make sure that the views look as you expect them to and that the second one opens as a modal. It should slide up from the bottom.
Now open the DisplayViewController in Xcode’s Assistant Editor and control-drag to create an outlet for the second label named “startDateLabel”. Also create a variable of type NSDate named “savedDate”. Finally, create a constant named userCalendar to store NSCalendar.currentCalendar(). Here’s what your outlet, variables and constant should look like in the DisplayViewController:
@IBOutlet weak var startDateLabel: UILabel! var savedDate: NSDate = NSDate() let userCalendar = NSCalendar.currentCalendar()
Add a new ViewController file to your project named “EditViewController”. This should be a Cocoa Touch Class file that is a subclass of UIViewController. Save this new file and return to the storyboard. Click on the view with the datepicker and in the Identity Inspector change the class dropdown to “EditViewController”.
Now that the view and EditViewController are linked, open EditViewController in Xcode’s Assistant Editor and control-drag to create an outlet for the date picker and name it “startDatePicker”.
Add the following method to EditViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // send values to display view controller if let destination = segue.destinationViewController as? DisplayViewController { destination.savedDate = startDatePicker.date } }
This method sets the value of the savedDate variable that we placed on the DisplayViewController to the value of the date picker on the EditViewController when the user segues from one to the other. The segue is triggered by the user hitting the “Save” button as we will see soon.
Coding the Save
In order to share data between the iPhone app and the watch app/extension, we need to add an import statement for WatchConnectivity at the top of the DisplayViewController file, add a declaration to implement the WCSessionDelegate protocol and finally create a variable named session to store WCSession!:
import UIKit import WatchConnectivity class DisplayViewController: UIViewController, WCSessionDelegate { @IBOutlet weak var startDateLabel: UILabel! var savedDate: NSDate = NSDate() let userCalendar = NSCalendar.currentCalendar() var session : WCSession!
These changes will allow us to save data to session for use by the watch app/extension.
In the same file, add the following code to the ViewDidLoad method of our DisplayViewController:
override func viewDidLoad() { super.viewDidLoad() if (WCSession.isSupported()) { session = WCSession.defaultSession() session.delegate = self; session.activateSession() } // get values from user defaults let defaults = NSUserDefaults.standardUserDefaults() if let dateString = defaults.stringForKey("dateKey") { startDateLabel.text = dateString } }
Now add the following function to the same file, DisplayViewController:
@IBAction func saveSegue(segue:UIStoryboardSegue) { let dateFormat = NSDateFormatter() dateFormat.calendar = userCalendar dateFormat.dateFormat = "yyyy-MM-dd" let savedDateString = dateFormat.stringFromDate(savedDate) startDateLabel.text = savedDateString // save to user defaults for use here in phone app let defaults = NSUserDefaults.standardUserDefaults() defaults.setObject(savedDateString, forKey: "dateKey") // save values to session to be shared with watch let applicationDict = ["dateKey":savedDateString] do { try session.updateApplicationContext(applicationDict) } catch { print("Error updating application context.") } }
This function responds to the save segue initiated by the user when they hit “Save” on the edit view. It formats the saved date as a string and saves it to the user defaults for use in the iPhone app and to session for use in the watch app/extension.
To connect the Save button on the EditViewController to the new saveSegue function on the DisplayViewController open the main.Storyboard and control-drag from the Save button to the orange Exit icon on the EditViewController. Select “saveSegue” in the small Action Segue popup that appears.
Now that we have the date stored in User Defaults, we can set the datepicker control to our saved date when we load the Edit view. To do this add the following code to the EditViewController’s viewDidLoad() method, above the “super.viewDidLoad()” line:
// get value from user defaults and set datepicker let defaults = NSUserDefaults.standardUserDefaults() if let dateString = defaults.stringForKey("dateKey") { let userCalendar = NSCalendar.currentCalendar() let dateFormat = NSDateFormatter() dateFormat.calendar = userCalendar dateFormat.dateFormat = "yyyy-MM-dd" startDatePicker.date = dateFormat.dateFromString(dateString)! }
Re-test your app and now you should see your saved date appear in the datepicker on load of the Edit view.
Using Saved Date in Watch
Now that we have added the ability to save a new start date in our phone app it is time to use that date in our WatchKit Extension.
In the WatchKit Extension project, open the InterfaceController.swift file. To be able to share data between the watch and the phone apps we will make changes to this file that are very similar to what we did earlier in the phone app’s DisplayViewController: add an import statement for WatchConnectivity at the top of the file, add a declaration to implement the WCSessionDelegate protocol and finally create a variable named session to store WCSession:
import WatchKit import Foundation import WatchConnectivity class InterfaceController: WKInterfaceController, WCSessionDelegate { @IBOutlet var yearsLabel: WKInterfaceLabel! @IBOutlet var monthsLabel: WKInterfaceLabel! @IBOutlet var daysLabel: WKInterfaceLabel! var session : WCSession!
In the same file, add an override to the Init() function to initialize our new session variable:
override init() { super.init() if (WCSession.isSupported()) { session = WCSession.defaultSession() session.delegate = self session.activateSession() } }
Again in the same InterfaceController file we will add a function named session that will pull our date from application context and save for use in user defaults:
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) { // get values from app context let displayDate = (applicationContext["dateKey"] as? String) // save to user defaults let defaults = NSUserDefaults.standardUserDefaults() defaults.setObject(displayDate, forKey: "dateKey") }
Now we will replace the following code in the startCountdown function in the InterfaceController:
// hard-code start date value for part one of this tutorial let startDateIntertech: NSDate = dateFormatter.dateFromString("2005-06-01")!
with the following code which gets the start date from user defaults:
// get values from user defaults let defaults = NSUserDefaults.standardUserDefaults() var startDateIntertech = NSDate() if let dateString = defaults.stringForKey("dateKey") { startDateIntertech = dateFormatter.dateFromString(dateString)! }
Now when you edit your start date in the iPhone app it will update the display on the watch. Congratulations!
The code for this Apple Watch development tutorial is on GitHub. If you are having trouble be sure to check out my article on Apple Watch Developer Tips and Tricks.
In this second part of this tutorial series we built a functioning iOS app for the iPhone that allows our users to select their start date on the phone. This start date is then used in our watchOS 2 app. Come back next week for part 3 of this Apple Watch development tutorial series where we will add a complication to the face of our Apple Watch.
Check out the other posts in the series:
Apple Watch Development Tutorial (Part 1): Countdown App