Building Apple Watch Projects
上QQ阅读APP看书,第一时间看更新

Getting user input

So far, we have had very little input from the user. She has launched the app and pressed a button, but the Watch is good for more than just presenting information. While the size of the screen makes some forms of input impractical, and text input via a keyboard must be top of that list, there are still many user input methods, both old and new at our disposal.

WatchKit's text input controller is a very simple way to gather text input (as the name might suggest) using the presentTextInputControllerWithSuggestions method provided by WKInterfaceController. When making this method call, you provide a list of text options from which the user may make a selection (she may also cancel, or choose voice input).

Firstly, we want to modify changeBorderColor() to accept a String argument which will tell it what color the user has selected. Replace the function as it stands with the following:

    func changeBorderColor(colorString: String) {
        let newColor: UIColor
        
        switch colorString {
        case kRed:
            newColor = UIColor.redColor()
        case kPurple:
            newColor = UIColor.purpleColor()
        case kBlue:
            newColor = UIColor.blueColor()
        default:
            return
        }
        
        animateWithDuration(2.5, animations: {
            self.borderGroup.setBackgroundColor(newColor)
        })
    }

The compiler will complain that it knows nothing of kRed, KPurple, and kBlue. These are constants we will create to prevent any typos creeping into our code, such as red instead of Red. Add these constant declarations directly after the import statements at the top of the code:

import WatchKit
import Foundation

let kRed = "Red"
let kPurple = "Purple"
let kBlue = "Blue"

The compiler warnings will now disappear.

Next, remove the code inside buttonTapped(), replacing it with a call to a new method presentColorOptionsToUser() and add the definition for the new method:

func presentColorOptionsToUser() {
    presentTextInputControllerWithSuggestions(
        [kRed, kPurple, kBlue], //1
        allowedInputMode: WKTextInputMode.Plain, //2
        completion:{(results: [AnyObject]?) -> Void in //3
            if let validResults = results, //4
                let resultString = validResults[0] as? String //5
            {
                self.helloButton.setTitle(resultString) 
                self.changeBorderColor(resultString)
            }
    })
}

This is quite an intense chunk of code, so let us take a detailed look at what we are doing.

  • We provide an Array of constant String values that are to be offered to the user.
  • allowedInputMode is set to .Plain, since we have no use for emojis in this particular context (!).
  • We specify the completion closure, which will be called when the user makes a selection (or cancels). This closure takes an optional array of objects, which we have called results; this array will contain the selected String, or nil if no String was selected.
  • We check that results is not nil.
  • We check that first object in the validResults array is indeed a String object and use that the String to set the title of the helloButton, and as the argument to changeBorderColor(colorString: String).

Check that your complete code, including the constant declarations, looks like this:

import WatchKit
import Foundation

let kRed    = "Red"
let kPurple = "Purple"
let kBlue   = "Blue"

class InterfaceController: WKInterfaceController {
    
    @IBOutlet var helloButton: WKInterfaceButton!
    @IBOutlet var borderGroup: WKInterfaceGroup!
    
    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
    }
    
    @IBAction func helloButtonTapped(){
        presentColorOptionsToUser()
    }
    
    func presentColorOptionsToUser() {
        presentTextInputControllerWithSuggestions(
            [kRed, kPurple, kBlue],
            allowedInputMode: WKTextInputMode.Plain,
            completion:{(results: [AnyObject]?) -> Void in
                if let validResults = results,
                    let resultString = validResults[0] as? String
                {
                    self.helloButton.setTitle(resultString)
                    self.changeBorderColor(resultString)
                }
        })
    }
    func changeBorderColor(colorString: String) {
        let newColor: UIColor
        
        switch colorString {
        case kRed:
            newColor = UIColor.redColor()
        case kPurple:
            newColor = UIColor.purpleColor()
        case kBlue:
            newColor = UIColor.blueColor()
        default:
            return
        }
        
        animateWithDuration(2.5, animations: {
            self.borderGroup.setBackgroundColor(newColor)
        })
    }
}

When you run the app, tapping the helloButton will now bring up a modal screen offering you the options seen in the screenshot below:

The Cancel button is created automatically by WatchKit. Tapping it will dismiss the modal view, and also return nil to the closure that we provided when calling presentTextInputControllerWithSuggestions, and so our results array will be nil. Our code checks for nil, and thus returns without doing anything.

Tapping the microphone icon will also do nothing; it will not even dismiss the modal view, since Watch Simulator does not handle voice input.

As we can see, the list of text options simply mirrors the array you passed as first argument into presentTextInputControllerWithSuggestions. Tapping one of these will dismiss the modal view and return the appropriate String to the closure, and thus we can use the results value to extract that String.

Did you really read all that before tapping one of the options? Either way, on tapping a color, you will see our button's apparent border (really a Group object) animate to its new color and change its title to reflect the selected text.

A subtle but pleasing animation, I am sure you will agree.