Creating Text Fields in SpriteKit

When creating labels in SpriteKit, a developer should use SKLabelNode, rather than using the UILabel class, as commonly done with UIKit. However, if you want to add a text field in a SpriteKit scene, there is no SKNode class for a text field. Instead, you have to go against the general convention of not using UIKit classes in a SpriteKit scene and use UITextField, as there is no analogous class in SpriteKit.

Creating a field (e.g., "textField" below) in SpriteKit is almost the same as creating an SKLabelNode. In the didMoveToView() function, you can declare as text field as:

let textField = UITextField()

Place this at the top of your GameScene class, so that it will be global and can be called by touchesBegan() if needed.

Modifying Parameters

Once textField has been initialized, you can modify the parameters of the text field object. To create a text field that has rounded borders with black text in the helvetica font with a 16 point size, you can do the following. First, create the border:

textField.frame = CGRectMake(view.bounds.width / 2, view.bounds.height / 2, 300, 40)

The CGRectMake() method positions a text field at x,y (which, in this case, is the relative center of the screen at x = view.bounds.width / 2 and y = view.bounds.height / 2) and creates a field that is 300 by 40 pixels. To move the text field to the left of center, subtract pixels from view.bounds.width / 2. To move the text field up from center, subtract pixels from view.bounds.height / 2. For example, to move the text field 150 pixels to the left of center and 100 pixels above the center, the x,y point would be view.bounds.width / 2 - 150, view.bounds.height / 2 - 100.

Then modify the other parameters:

textField.borderStyle = UITextBorderStyle.RoundedRect
textField.textColor = UIColor.blackColor()
textField.font = UIFont(name: "helvetica", size: 16)

Then, to add the text field to your scene, program the following:

self.view?.addSubview(textField)

Adding Actions

OK, now you've added your text field to the scene. What if you want to add an action when someone taps on the field? I suppose you could use the touchesBegan() function, however, you can stick with the UIKit methodology. In this case, we will first add a function that will do something when the text field is tapped. Let's call this function tapTextField and place it outside of didMoveToView() but within the GameScene class, right under your global declaration of let textField = UITextField():

func tapTextField(textField: UITextField) {
    textField.text = "got it!"
}

With that function, the text field will be passed to textField so that you can do operations on it. In this case, the text field's text property will be changed to "got it!"

Within didMoveToView(), go back to your lines where you specified the textField properties. Add the following addTarget property. This will fire off the tapTextField function when the text field is tapped:

textField.addTarget(self, action: #selector(GameScene.tapTextField), forControlEvents: UIControlEvents.TouchDown)

(In Swift 2.2 and beyond, you have to use #selector(GameScene.tapTextField) instead of Selector: "tapTextField:". Note that you have to specify the class GameScene with the new method.)

Now, when you tap on the text field, the words "got it!" should appear in the text field.

You can also use other UIControlEvents for the addTarget property other than TouchDown, such as:

  • TouchDownRepeat
  • TouchDragInside
  • TouchDragOutside
  • TouchDragEnter
  • TouchDragExit
  • TouchUpInside
  • TouchUpOutside
  • TouchCancel
  • ValueChanged
  • EditingDidBegin
  • EditingChanged
  • EditingDidEnd
  • EditingDidEndOnExit

Transitioning to Another Scene

When transitioning to another scene, you will have to manually remove the text fields. To do so, you can programmatically do the following:

self.textField.removeFromSuperview()

However, as a rule of thumb, all UI changes should be done in the main thread. If you need to remove the text field outside of the main thread (e.g., in touchesBegan()), you will need to use the Grand Central Dispatch (GCD):

dispatch_async(dispatch_get_main_queue(), {
    self.textField.removeFromSuperView()
})

This will allow you to perform an action in another thread so that XCode doesn't throw an error.