Make App Pie

Training for Developers and Artists

Swift WatchKit: Adding Text, Dictation, and Emoji Input to Apple Watch 

Photo Jun 18, 6 24 31 AM

Sometimes Apple gives away stuff you can get very happy about — like a chance to use voice dictation. If you have had a chance to play with an Apple Watch, the watch has a simple but great way to deal with text messages. If you get a text message, you can hit reply and a special view comes up where you can reply to the message in one of three ways. You can use a standard message from a list, dictate a response, or you can add an emoji, including an animated one. The good news is this functionality is a single method presentTextInputControllerWithSuggestions, and any developer can use it in their own watch apps.

Creating the Target and Extension

Create a new project for your phone by pressing  Command-Shift-N and picking a single view template.  Name the project SwiftWatchInput using Swift  as the language.  Leave the device type as Universal.  Save the project.

From the editor, select Editor> Add Target…

Screenshot 2015-04-12 11.58.10

Another window pops up asking which template to use for the template. Use the  WatchKit App Template found under Apple Watch  

image

You will get another window asking what configuration you want:

image

For this  app, uncheck  the Include Notification Scene. Press Finish.  You will get this message:

image

Activate the scheme. You will be in the extension. Go to the storyboard for the watch app. Drag a label and a button on the interface. Set the button to a light background  color, and vertically position it Bottom. Title the button Text Input.
Make the label centered, and three lines:

2015-06-18_07-34-58

When done your interface should look like this.

2015-06-18_07-24-26

Open the assistant editor. Control-drag from the label to the code and add an outlet myLabel. Then control-drag from the button to the code and make an action called textInputPressed.

Add the Code

Close the assistant editor and go to the InterfaceController.swift code in the extension group.   Add the following code to the textInputPressed action

let textChoices = ["Yes","No","Maybe","I'll think about it","Just Order Pizza", "Out on a run, later dude", "Seriously, just order Pizza"]

The array textChoices will be the choices we add to the text input. Frequent choices for input can be placed in this array and will show up as a button in a list the user can scroll through and pick.

Present the controller by adding this code below the textChoices assignment:

presentTextInputControllerWithSuggestions(textChoices,
     allowedInputMode: WKTextInputMode.Plain,
     completion: {(results) -> Void in })

The first parameter loads our choices into the text input controller.  the second parameter sets the input mode. There are three modes which are choices from  WKTextInputMode:

  • .Plain — This allows text only.  You will have a button for dictation on the bottom of the interface.
  • .AllowEmoji — This allow text and non-animated(character-based)  Emoji. You have buttons on the interface for emoji and dictation.
  • .AllowAnimatedEmoji — This allows text and Animated Emoji( UIImage animations). You have buttons on the interface for emoji and dictation.

The last parameter is a closure to run upon completion of the input controller.  This is where you process your input and use it in your application. Unlike other completion handlers, this cannot be nil.  For the moment we put in the minimal amount for the closure, and we’ll come back to it shortly.

Build and run.  When the watch app shows up in the simulator,  Press the Text Input button and you will see this:

2015-06-18_07-50-36

Scroll down and you will see some other choices from the array

2015-06-18_07-51-03

Press Just Order Pizza and you are back to the root view. Nothing happens. We need to put something in the completion handler.

Coding the Closure

If you are not familiar with closures, they are a common way in Swift to code completion handlers.  A completion handler is a set of code that runs after a function completes. As in the case with this modal controller, they often are code to transfer data from a modal controller to the controller calling it. The closure passes the code as a parameter in a special syntax. Closures start as a code block of curly braces, and have some required stuff in it.  A  generic closure looks like this

{(parameter) -> ReturnType in code}

Closures are functions run inside of another function’s parameter.  In our case we have a closure of

{(results) -> Void in}

Which has a parameter of  results and returns nothing. It also has no code so it does nothing. Change the code to this, adding a closure:

presentTextInputControllerWithSuggestions(textChoices,
    allowedInputMode: WKTextInputMode.AllowEmoji,
    completion: {(results) -> Void in
        if results != nil && results!.count > 0 { //selection made
            let aResult = results?[0] as? String
            self.myLabel.setText(aResult)
        }
    }
)

The  text input method returns a value of [AnyObject]! which we use as a parameter result in the closure.  We first check if we selected something.  A nil value meant we cancelled, and a count of 0 means there is nothing in the array. If we have something there, we have a selection. It’s most likely a string, so we cast it to String and set the label’s text with this string.

Build and Run. Tap the Text Input button:

2015-06-18_07-50-36

This time, go to the input interface, and tap Yes. When you do, the label changes to Yes.

Dictation and Emoji in the Simulator

Press the text input button again and try adding an emoji by tapping on the smiling emoji button.  You get this in the console:
2015-06-18 07:51:27.760 WatchInputAndMenus WatchKit Extension[1882:40987] Emoji is not supported in the WatchKit Simulator

If you try tapping on the microphone to dictate, you get this in the console:
2015-06-18 07:51:37.099 WatchInputAndMenus WatchKit Extension[1882:40987] Dictation is not supported in the WatchKit Simulator

As the simulator tells you, Emoji and dictation are not supported in the simulator. You need an Apple Watch to test them Fortunately, mine finally showed up, so I’ll show you what the watch  does when you use these buttons.

If I press the smiling emoji button, we get a list of emoji, starting with my favorites.

IMG_1697

I tap a clapping emoji, and it shows up in the label. Flat emoji are characters so they work fine with the current application.

IMG_1698

Tap the Text Input button again, and then tap the dictation button. I get a screen similar to a Siri request screen. I talk and the words appear on the top part of the screen.

IMG_1703

press Done and the words appear

IMG_1701

Animated Emoji

 

If you use  .AllowEmoji or .Plain for your text input mode, that is all you need to know.  The text input mode also supports the animated emoji, whihc are a different data type NSData.  This presents us with several problems.  we cannot guarantee that the data type, and have to check the type before we grab the string.  This is a good place for optional chaining of the string result. Change the code to this:

presentTextInputControllerWithSuggestions(textChoices,
    allowedInputMode: WKTextInputMode.AllowAnimatedEmoji,
    completion: {(results) -> Void in
        if results != nil && results!.count > 0 { //selection made
           if let aResult = results[0] as? String{
                self.myLabel.setText(aResult)
            }else{
                self.myLabel.setText("Animated Emoji not implemented")
            }
        }
    }
)

Using optional chaining, we assign aResult just as before. It’s now part of an if..else statement that if we can assign results[0] to aResult will use the value of aResult as a string, and if not, will assume our data type is NSData for an animated emoji. Animation is too complex to cover in this lesson, so I just tell the user this was an animated emoji, and I’m not going to do anything with it.

For most cases we don’t need the animated emoji. The earlier, easier code for the .Plain or the .AllowEmoji will work perfectly. In our next lesson, we’ll look at making menus from a force press.

The Whole Code

//
//  InterfaceController.swift
//  SwiftWatchInput WatchKit Extension
//
//  Created by Steven Lipton on 6/17/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {

    @IBOutlet weak var myLabel: WKInterfaceLabel!
  
    @IBAction func getTextInput() {
        let textChoices = ["Yes","No","Maybe","I'll think about it","Just Order Pizza", "Out on a run, later dude", "Seriously, just order Pizza!!"]
        presentTextInputControllerWithSuggestions(textChoices,
            allowedInputMode: WKTextInputMode.AllowEmoji,
            completion: {(results) -> Void in
                if results != nil && results!.count > 0 { //selection made
                    let aResult = results?[0] as? String{
                    self.myLabel.setText(aResult)
                }
        })
        
    }
    
    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()
    }

}

4 responses to “Swift WatchKit: Adding Text, Dictation, and Emoji Input to Apple Watch ”

  1. Can’t get this to work in Xcode 7 beta 6. looks like the presenttextInput method has changed

    1. I’m looking into this using watchOS2. iOS9 is even more cranky about optionals it looks like. I’m getting an error here:
      let aResult = results[0] as? String
      it should be
      let aResult = results?[0] as? String

      Simulator is being annoying again. I’ll look into any other changes, but there is no documentation changes. If you could describe the problem better, it might help.

      Update: Works fine in WatchOS2 on Beta 6 when loaded to my watch. I suspect your problem is the one above.

  2. got a modified version to work in the early hours of the morning :)

        @IBAction func btnInputNotes() {
       //     let textChoices = ["Yes","No","Maybe","Are you crazy","Lol", "Doh", "Seriously"]
    
            presentTextInputControllerWithSuggestions(nil, allowedInputMode: WKTextInputMode.Plain) { results in
                guard let results = results else {
                    //User tapped cancel on the text input controller
                    return
                }
                
                for result in results {
                    if let inputText = result as? String {
                        //User inputted text
                        print(inputText)
                        self.lblNotes.setText(inputText)
                        self.lblNotes.setHidden(false)
                        self.storeNote(inputText)
                    }
                 }
            }
    
  3. Yes, for..in and then optionally bind will also solve the problem. Great solution!!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: