One commonly requested feature is a “widget-based” text input field and today’s tutorial gives you a foundation to build your own. This isn’t a complete implementation, and it only overcomes a few issues related with native.newTextField(), but it should provide a good starting point.

Let’s begin with some basic setup code:

local myTextField = widget.newTextField(
   top = 10,
   left = 20,
   width = 200,
   height = 30,
   cornerRadius = 8,
   strokeWidth = 3,
   backgroundColor = { 1, 1, 1 },
   strokeColor = { 0, 0, 0 },
   font = "Helvetica",
   fontSize = 24,
   listener = textFieldHandler

Naturally, a widget like this could end up with an enormous number of options, but let’s start by considering some basic options:

  • top — distance from the top of the screen
  • left — distance from the left of the screen
  • x — location of the widget horizontally (center)
  • y — location of the widget vertically (center)
  • width — width of the field
  • height — height of the field
  • font — Font to use
  • fontSize — size of the font
  • backgroundColor — the color behind the text
  • strokeColor — the color of the stroke around the field
  • strokeWidth — the size of the stroke
  • cornerRadius — if you want rounded corners
  • text — the initial text
  • inputType — the type of keyboard to show
  • listener — the input handler

Obviously this could be extended to include things like the value of the “return” button, having placeholder text that disappears when you first start editing the field, and even skinning features. However, since this function is going to use a native text field, we’ll need to write — at the core — a function to handle editing of the field:

local function textFieldHandler( event )

   -- "event.text" only exists during the editing phase to show what's being edited; 
   -- it is NOT the field's ".text" attribute (that is "").

   if ( event.phase == "began" ) then
      -- user begins editing textField
      print( "Begin editing", )
   elseif ( event.phase == "ended" or event.phase == "submitted" ) then
      -- do something with defaulField's text
      print( "Final Text: ", )
      native.setKeyboardFocus( nil )
   elseif ( event.phase == "editing" ) then
      print( event.newCharacters )
      print( event.oldText )
      print( event.startPosition )
      print( event.text )

If we want this to be part of the widget library, we simply add it to a module and include the widget library:

local widget = require( "widget" )
function widget.newTextField( options )

This will cause a new function, newTextField(), to be added to the instance of the widget library that has been included in your project. Then, in any scene or module where you require the widget library, the function will be available.

Now let’s define the default option values:

function widget.newTextField( options )
   local customOptions = options or {}
   local opt = {}
   opt.left = customOptions.left or 0 = or 0
   opt.x = customOptions.x or 0
   opt.y = customOptions.y or 0
   opt.width = customOptions.width or (display.contentWidth * 0.75)
   opt.height = customOptions.height or 20 =
   opt.listener = customOptions.listener or nil
   opt.text = customOptions.text or ""
   opt.inputType = customOptions.inputType or "default"
   opt.font = customOptions.font or native.systemFont
   opt.fontSize = customOptions.fontSize or opt.height * 0.67

   -- Vector options
   opt.strokeWidth = customOptions.strokeWidth or 2
   opt.cornerRadius = customOptions.cornerRadius or opt.height * 0.33 or 10
   opt.strokeColor = customOptions.strokeColor or { 0, 0, 0 }
   opt.backgroundColor = customOptions.backgroundColor or { 1, 1, 1 }

The code above may seem a bit confusing, but we’re simply accepting a parameter to the function called options. The first line creates a table named customOptions which makes sure it’s a Lua table. If you don’t pass the options parameter, an empty table is created. After that, we just set each of the individual options to either the passed value or a default value. For this widget, things like the corner radius and the font size should default to values that adapt to the size of the field.

Creating the visual elements

In this section, we’ll create the visual part of our text field widget, including the native UI element to pair it with:

local field = display.newGroup()

local background = display.newRoundedRect( 0, 0, opt.width, opt.height, opt.cornerRadius )
background:setFillColor( unpack(opt.backgroundColor) )
background.strokeWidth = opt.strokeWidth
background.stroke = opt.strokeColor
field:insert( background )

if ( opt.x ) then
   field.x = opt.x
elseif ( opt.left ) then
   field.x = opt.left + opt.width * 0.5
if ( opt.y ) then
   field.y = opt.y
elseif ( ) then
   field.y = + opt.height * 0.5

-- Native UI element
local tHeight = opt.height - opt.strokeWidth * 2
if "Android" == system.getInfo("platformName") then
    -- Older Android devices have extra "chrome" that needs to be compesnated for.
    tHeight = tHeight + 10

field.textField = native.newTextField( 0, 0, opt.width - opt.cornerRadius, tHeight )
field.textField.x = field.x
field.textField.y = field.y
field.textField.hasBackground = false
field.textField.inputType = opt.inputType
field.textField.text = opt.text
print( opt.listener, type(opt.listener) )
if ( opt.listener and type(opt.listener) == "function" ) then
   field.textField:addEventListener( "userInput", opt.listener )

local deviceScale = ( display.pixelWidth / display.contentWidth ) * 0.5

field.textField.font = native.newFont( opt.font )
field.textField.size = opt.fontSize * deviceScale

Here are a few points to consider as you inspect this code:

  • If you round the corners, don’t let the actual text field extend into the corners.
  • Remember to hide the native text field background so that your custom visuals show up.
  • native.newTextField() needs the font string name converted to a native.newFont(). However, the size is a bit tricky because native text fields do not automatically scale. Thus, we must calculate the device’s real scale factor and then multiply the desired font size by that scale factor.

Removing the elements

We need to make sure that when the widget gets removed, the native text field gets removed as well. Previously, we would need to override the removeSelf() function with our own function that would eventually call the original removeSelf(). However, the new finalize event makes this process easier. This allows us to set up a function that’s executed just before the display object is removed from the stage, in case there are related cleanup tasks we need to handle — in this case, removing the native text field just before our custom text field group is removed.

function field:finalize( event )
field:addEventListener( "finalize" ) 

return field

Using the custom text field

Using our new “widget” text field is simple. To place it on screen, our code may look like this:

local myTextField = widget.newTextField(
   width = 250,
   height = 30,
   text = "Hello World!",
   fontSize = 18,
   font = "HelveticaNeue-Light",
   listener = textFieldHandler

myTextField.x = display.contentCenterX
myTextField.y = 100

Retrieve the value of the text field

local myText = myTextField.textField.text

Set the value of the text field

myTextField.textField.text = "Edited Value"

In summary

This tutorial should get you started with implementing “styled” text input fields and, hopefully, extending them to more complex use cases.

Share this post....Share on Facebook0Share on Google+8Tweet about this on TwitterShare on LinkedIn5
  1. Rob, great tutorial but…

    I got super excited for a moment thinking this would resolve the biggest issue with textFields, ie the font size scaling on various devices, especially Android.

    Every time I do a business app, this one issue always bites me, as with many others.

    Hopefully we can get the font size scaling issue resolved soon as it’s been plaguing me and many other developers for a very long time.

    Please try and find a solution for this issue.

    • All display objects need to be explicitly removed and nil’ed with the exception of Storyboard where display objects in the scene’s view are removed for you when the scene is destroyed.


      • Sorry, I wasn’t precise enough; that probably sounded like a bit of a trivial question. I meant is removeSelf() still necessary for widgets versus, say, group:remove()’ing or removing the parent group, on account of the logic being moved into the finalizer. At least, I’d assumed those don’t invoke the (possibly overridden) removeSelf() method…

        Of course, maybe I’ll test and see my assumptions were wrong all along. :)

  2. Thanks for a great tutorial. This is a great starting point for what I hope will evolve into a formal Corona Labs widget.newTextField() soon.

    Question – how can we extend the functionality so that if a new parameter (ie eraseInitialText) is set to true then the initially supplied text is erased when user first taps into this input field? I am able to do this in the began field of my listener function but I would like to push this code into the widget extension code so it can be reused. Is this possible? Thanks

  3. Hey Rob, will these text fields scroll in a widget ScrollView now? That has always been a big issue for me; getting the text fields to scroll with the rest of the screen. Is there any sort of time frame on when this will be implemented in the actual widget code base?

    • I didn’t try it, but it should. The sync code keeps the native text field wherever the object is. Depending on how fast you scroll the scrollView there may be some lag while the system moves the native.newTextField.


  4. i use corona to teach students who use the simulator on mac or windows and who do not necessarily have a droid.

    “Since this module still utilizes native text fields, it will draw the widget background on the Windows Corona Simulator, but the text field will not function.”

    Really, how hard can it be to have a single line text box that is multi-platform?
    This is a huge restriction for classroom use.

    • We’ve explained this on many occasions. Corona SDK is based on OpenGL. On OS-X, iOS and Android, the OS’s permit native objects and the OpenGL canvas to occupy the same window. On Windows, they do not permit native objects and OpenGL objects to co-exist in the same window. Mircosoft lets this happen for DirectX based apps, but we are an OpenGL based app.

  5. widget.newTextField({}) is showing error in my code, while referencing to widget.
    does it support in pro-corona or i m doing something wrong in code?
    local widget = require(“widget”)
    local myTextField = widget.newTextField(
    top = 10,
    left = 20,
    width = 200,
    height = 30,
    cornerRadius = 8,
    strokeWidth = 3,
    backgroundColor = { 1, 1, 1 },
    strokeColor = { 0, 0, 0 },
    font = “Helvetica”,
    fontSize = 24
    –listener = textFieldHandler

  6. Is there any module or plugin anyone has come up which that offers typeahead / autocomplete functionality?
    So as the user types the first few letters, something in the UI near the text input suggests terms pulled from a lua table?
    This would be so good.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>