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…
Another window pops up asking which template to use for the template. Use the WatchKit App Template found under Apple Watch
You will get another window asking what configuration you want:
For this app, uncheck the Include Notification Scene. Press Finish. You will get this message:
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:
When done your interface should look like this.
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:
Scroll down and you will see some other choices from the array
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:
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.
I tap a clapping emoji, and it shows up in the label. Flat emoji are characters so they work fine with the current application.
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.
press Done and the words appear
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() } }
Leave a Reply