 WatchKit to Parse Tutorial - Part one

Connecting to Parse and using openParentApplication

This was an interesting learning curve for me, and figuring out the Async/ sync characteristics of openParentApplication() in watch kit had me stumped for a bit. I hope to demystify, and simplify opening the parent application from the watch app along with how we can use that with Parse

Overview

Xcode 6.3/ Swift 1.2
This tutorial covers pushing data from WatchKit to Parse in. This essentially becomes an exercise and tutorial on using openParentApplication, and it’s counterpart, handleWatchKitExtensionRequest; which we will go over in some detail.

The application we will be building is a simple, emoji status updater. On the watch, users will be able to select from a pre-defined set of emojis representing their current emotional state. They will then be able to keep track of their emotions over time by showing a history of how they felt and at what time. Pretty simple, but it will get the idea across quite well.

In Part one, we will set up the base iPhone app and the Watch UI, then we’ll hook up the watch to send our current status to Parse. Part two will deal with pulling the data back down and showing the history.

Step 1: Setup App/ WatchKit Extension
Step 2: Setup Parse on the iOS side
Step 3: The iOS app (Parse Test)
Step 4: WatchKit
Step 5: putting it all together

Starting a New WatchKit project

Open Xcode and start a new project. The project will be a single view iOS Single view application, go ahead and leave coreData checked for flexibility later if you want.

After the project is created, Add a new target with File > New > Target > iOS Apple Watch > WatchKit (confirm the activation when the popup opens to set the current target to the watch app)

Setting up and installing Parse framework

If you’re not familiar with Parse check it out here and create a free account.

Make a new project, it will be a Data project, for mobile, iOS, with a type of Swift, and choose existing project.

Follow the quick start guide to make sure you have all of the frameworks installed. You will need to download the Parse framework, as well as add the list of frameworks from the quick start guide

Creating a basic iOS App and testing the Parse connection

It is not entirely necessary to create an interface for the iOS app at this point. We will do that in Part two. It is important to test that Parse is working at this point.

Initializing and setting up the Parse connection.

If you’re following the quick start guide, you may have already done this. In the AppDelegate.swift (of the iOS app), paste in your Parse connection code (with your own application ID) inside the didFinishLaunchingWithOptions function at the top


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

// Initialize Parse with your own ApplicationID and ClientKey.
Parse.setApplicationId(“Wopiuhj1234097JADhl1987",
clientKey: “GGHGHGLglkjhf2394857HgfHJLj")


return true
}

Next, Open the View controller and paste the sample code into the viewDidLoad() function


let testObject = PFObject(className: "TestObject")
testObject["foo"] = "bar"

testObject.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
println("Object has been saved.")
}

Once you’ve finished that, running the iOS application should push the test object and you should be able to go to parse.com and see your new object by going to the Core section, under the Data left sidebar choosing the newly created TestObject class and there should be one row entry with the name “bar” in the “foo” column. This is pretty straightforward so far. We’ll be doing the same thing later on, only, from the watch with our own data.

Building the watch app

To start, we’ll build out the user interface. Keep it simple to start and lets only use 3 buttons.

In the storyboard, I’ve added a label, and a group with 3 buttons; each with a width set to Relative to container, 0.3333333. Changing the button titles to emojis. To bring up the emoji selector, use ctrl+cmd+spacebar

The second screen is pretty simple, it will only be 2 labels. Set the horizontal position of each one to center. For the first label, I’ve copied an emoji from the first scene and pasted it in, then adjusted the text size to 84. The second label simply says “success!” with the color changed to green.

After the labels are set up, add a new InterfaceController to the WatchKit Extension by going to new file, Cocoa Touch Class, and make a subclass of WKInterfaceController and assigning the second interfaceController on the storyboard to that new class. At the same time, give the InterfaceController an identifier, such as “SuccessScreen”

The first controller will have an @IBAction for each button, which will set the current emoji and present the second screen with that emoji. To present the second screen as a modal, we’ll use presentControllerWithName()

I’ve created a function for this, that takes in an emoji as an argument—emoji’s are just Unicode, and therefore strings. To use the function, copy and paste the corresponding emoji from the button to the @IBAction in the sendEmoji() func.

Notice, that in PresentControllerWithName we’ve used the previously set Identifier for the second InterfaceController.


@IBAction func pushedEmoji1() {
sendEmoji("😰")
}

@IBAction func pushedEmoji2() {
sendEmoji("😄")
}

@IBAction func pushedEmoji3() {
sendEmoji("😤")
}

func sendEmoji(emoji: String){
presentControllerWithName("SuccessScreen", context: emoji)
}

The last step in our basic UI is to hook up the success screen label to the emoji that we clicked on. To do this, first set the large emoji label as an @IBOutlet by ctrl+dragging to the interfaceController. In the didLoadWithContext function or the success screen controller set the text as the incoming context.

Since in the previous controller when we presented ControllerWithName we set the context to the emoji, we’ll now unwrap the context and use it to set the label.


@IBOutlet var emojiLabel: WKInterfaceLabel!

override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)

emojiLabel.setText(context as? String)

}

If everything is working, when you run the app in the simulator, you should be able to click on any of the emojis and have the success screen popup with that emoji!

Bonus if you’re up for it, you can add as many emojis at you want!

Communicating with iOS - openParentApplication

This function is the main line between the watch, and iPhone. There are some limitations however that we will need to work around, the main one being the parent iPhone app can only handle one handleWatchKitExtensionRequest (the counterpart of openParentApplication) the other being, that it doesn’t handle Async calls to parse very well.

The first part - Sending a request

Back in the mainInterfaceController, we’ll add the openParentApplication function and send the emoji to the parent application.

handleWatchKitExtensionRequest can only handle one request and only be used once per AppDelegate (where it will go), so we will send a request from openParentApplication so we can use an if statement later to sort multiple requests.

So, in the sendEmoji() function we’ll add the following:



var request = ["request": "pushEmoji", "emoji": emoji ]
WKInterfaceController.openParentApplication(request, reply: { (replyFromParent, error) -> Void in
//Stuff here happens in response to what parent app sends back

if error != nil{
println("there was an error receiving a reply")
}else{

println("everything Sent OK")
println(replyFromParent)
}


})

The variable request, gets sent to the iOS AppDelegate, after the phone is done with handling the WatchKit request it will send back a response. That response is what I’ve set as replyFromParent. In that completion block, we will be able to handle the data that is being sent back. Since all we are doing at this point is pushing data, there isn’t any to handle. Therefore, we will just print some lines to the console letting us know what has happened.

Running the app at this point will print the error statement, since we haven’t set up the handler in AppDelegate.

The second part - Handling in iOS AppDelegate

In AppDelegate.swift, in the iOS Application, we’ll start by adding the handleWatchKitExtensionRequest function, right below didFinishLaunchingWithOptions.

In the handle function, userInfo is the incoming request dictionary from WatchKit. So far, the only thing we’re sending over is "request": "pullEmoji", so lets start by setting up logic to unwrap our request



func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {

//unwrap the incoming userInfo, our request from WatchKit and check the "request": "..."
if let userInfo = userInfo, request = userInfo["request"] as? String{

if request == "pushEmoji"{

}

else{


}
}
}

With this logic, we’ll be able to pass in different requests and reply back to the watch accordingly. For now, we just have one request to worry about.

To see how reply’s work, we’ll add some simple statements to make sure everything is hooked up properly. The reply from the iOS app is called using reply(["key": "value"]).



func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {

//unwrap the incoming userInfo, our request from WatchKit and check the "request": "..."
if let userInfo = userInfo, request = userInfo["request"] as? String{

if request == "pushEmoji"{
reply(["reply": "this is your parent, running pushEmoji"])
}

else{
reply(["reply": "invalid or missing request"])

}
}
}

AWESOME!!

At this point, running the watch app in the simulator and selecting an emoji should push to the success screen and print the reply we setup in the iOS handler, since we have println(replyFromParent) in our pushEmoji function.

Last step, adding in Parse

Now that the watch app is communicating with the parent iOS, all we need to do now is pass the emoji from the watch to the parent and push it Parse.

We only need to a line to the watch app pushEmoji function to send the emoji to the parent app. in the request definition, we add one more key:value pair.



var request = ["request":"pushEmoji", "emoji": emoji]

The rest of the logic goes into the handler function in AppDelegate. We need to write a little bit to unwrap the incoming emoji (which will be coming in through userInfo) and similar to the Parse test with testObject["foo"] = "bar", we’ll push it to our Parse Database.

*Make sure you import Parse and Bolts to AppDelegate if you haven’t already done so.

To keep things simple, we’ll write everything in the if request == "pushEmoji" block, but feel free to separate the parse request into it’s own function. Similar to how we unwrapped the request, we’ll do the same to the incoming emoji in userInfo


//Push incoming to Parse, using the emoji value sent from the Watch App

if let emoji: AnyObject = userInfo["emoji"] as? String{

//The Parse Business
let newStatus = PFObject(className: "QuickStatuses")
newStatus["emojiStatus"] = emoji as! String
newStatus.save()
reply(["reply":"this is your Parent, we're OK"])

}

If everything is hooked up, running the watch app and choosing and emoji should print the successful reply from parent and push the emoji to Parse! Check your parse Core, and it should be there

Asynchronous pushing

Due to the nature of the handleWatchKitExtensionRequest, if we used an Async call to parse, such as saveInBackgroundWithBlock, it will still work, and push to Parse, but we won’t be able to send back a reply from within that block, if it makes sense. Here is an example:



let newStatus = PFObject(className: "QuickStatuses")
newStatus["emojiStatus"] = emoji as! String
newStatus.saveInBackgroundWithBlock({ (objects, error) -> Void in
if error != nil{
reply(["reply":"Error, but it won't reply"])
}else{
reply(["reply":"this is your Parent, we're OK, but you won't know"])
}
})

That is perfectly fine as long as you don’t need to send back a success status that you will have the watch app process. The real issue with replying without Async calls is when we’re trying to pull data from Parse Asynchronously, since the iOS app will reply to the watch before we get the data.

Resources

Bezel - The simulator that looks like a watch