02 December 2014
Tutorial: Sizing Text Input Fields
NOTE: New features have been added to Corona SDK Daily Build 2014.2520 that handles the same tasks that this tutorial covers. If you’re using that build or later, you do not need the code presented below, but for those developers using builds earlier than 2014.2520, this tutorial is still useful for purposes of sizing native text input fields.
In a previous tutorial, we illustrated how to add native text inputs into a display.newGroup() or a Composer scene, allowing them to be moved as a unified group. This week, we’ll discuss how to elegantly configure both the font size and overall size of native.newTextField() input fields.
The reason why native text input objects are challenging is because Corona does not automatically scale fonts in any native UI, including native input fields and other native objects such as web views. Instead, these native objects use the system’s default font size, so while the defined content area (and the native objects) may be scaled relative to the actual content area, their font sizes are not. Consider this simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
-- config.lua application = { content = { width = 320, height = 480, scale = "letterbox", } } -- main.lua local centerX = display.contentCenterX local centerY = display.contentCenterY local myText = display.newText( centerX, centerY, "Hello World", "Helvetica", 20 ) local myInputField = native.newTextField( centerX, centerY+80, 200, 30 ) myInputField.size = 20 |
With a content area of 320×480, one pixel would equal one “point” measurement on an older device like the iPhone 3GS. In this case, because we defined the text size of both objects as 20
, the myText
text object would be 20 pixels (and points) tall and the native input’s text would also be 20 pixels (and points) tall. However, if we tried this same code on an iPhone 4S with its real screen being 640×960 — exactly double the size as the 3GS — Corona would automatically scale the myText
text to be 40 pixels tall. The device would scale the text field font and the text field based on it’s point’s system. iOS would double the size on Retina displays. Android would scale up depending on its DPI settings.
Implementing Scaling
There are two options to consider when approaching the sizing of text input fields:
- Make the text inside the native.newTextField() match the height of display.newText() labels or other related text, while the height of the overall input box remains variable.
- Make the height of the native.newTextField() fixed and force its text to fit inside.
There are, of course, considerations regarding both methods. Using fixed-height boxes is easier for layout purposes, but matching other text object sizes may look better. Ultimately, the UI design and layout will dictate which approach is best.
Now, let’s create two utility functions and attach them to the native.* library since they are related to native text input fields. In this way, both functions become part of the native library and you can call them throughout a project.
Making the Field Fit the Text
This first function makes the input field boundaries properly fit around the text. It returns a native.newTextField() along with the scaled font size.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
function native.newScaledTextField( centerX, centerY, width, desiredFontSize ) -- Corona provides a resizeHeightToFitFont() feature on build #2520 or higher if ( tonumber( system.getInfo("build") ) >= 2014.2520 ) then local textField = native.newTextField(centerX, centerY, width, 30) local isFontSizeScaled = textField.isFontSizeScaled textField.isFontSizeScaled = true textField.size = desiredFontSize textField:resizeHeightToFitFont() textField.isFontSizeScaled = isFontSizeScaled return textField end local fontSize = desiredFontSize or 0 -- Create a text object, measure its height, and then remove it local textToMeasure = display.newText( "X", 0, 0, native.systemFont, fontSize ) local textHeight = textToMeasure.contentHeight textToMeasure:removeSelf() textToMeasure = nil local scaledFontSize = fontSize / display.contentScaleY local textMargin = 20 * display.contentScaleY -- convert 20 pixels to content coordinates -- Calculate the text input field's font size and vertical margin, per-platform local platformName = system.getInfo( "platformName" ) if ( platformName == "iPhone OS" ) then local modelName = system.getInfo( "model" ) if ( modelName == "iPad" ) or ( modelName == "iPad Simulator" ) then scaledFontSize = scaledFontSize / ( display.pixelWidth / 768 ) textMargin = textMargin * ( display.pixelWidth / 768 ) else scaledFontSize = scaledFontSize / ( display.pixelWidth / 320 ) textMargin = textMargin * ( display.pixelWidth / 320 ) end elseif ( platformName == "Android" ) then scaledFontSize = scaledFontSize / ( system.getInfo( "androidDisplayApproximateDpi" ) / 160 ) textMargin = textMargin * ( system.getInfo( "androidDisplayApproximateDpi" ) / 160 ) end -- Create a text field that fits the font size from above local textField = native.newTextField( centerX, centerY, width, textHeight + textMargin ) textField.size = scaledFontSize return textField, scaledFontSize end |
This function first creates a standard text object in the specified size. Once created, Corona gets this text object’s display.contentHeight() and then immediately disposes of it. This is an important step because each device’s OS renders text a bit differently and we need a basis to measure upon. The next step is to calculate the scaled font size and the combined vertical margin for the input field. This considers Corona’s content scaling as well as various device adjustments. For iOS, this means handling iPhone and iPad a little different. For Android, we can adjust using the difference between the device’s real DPI and the 160 points-per-inch visual scale.
With the calculations complete, a native.newTextField() is created, its .size
is set to scaledFontSize
, and the object is returned to the caller along with the scaled font size value.
Making the Text Fit the Field
This next function is useful when the input field should be a fixed height and its text must fit within its boundaries. Unlike the above function which creates the text field, we’ll need to create a text field first and pass it to this function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
function native.getScaledFontSize( textField ) -- Use Corona's auto font sizing feature if running on build #2520 or higher -- For best performance, use resizeFontToFitHeight() instead of this native.getScaledFontSize() function if ( tonumber( system.getInfo("build") ) >= 2014.2520 ) then local previousFontSize = textField.size textField:resizeFontToFitHeight() local bestFitFontSize = textField.size textField.size = previousFontSize return bestFitFontSize end local fontSize = 10 local textMargin = 20 * display.contentScaleY -- convert 20 pixels to content coordinates local platformName = system.getInfo( "platformName" ) if ( platformName == "iPhone OS" ) then local modelName = system.getInfo( "model" ) if ( modelName == "iPad" ) or ( modelName == "iPad Simulator" ) then textMargin = textMargin * ( display.pixelWidth / 768 ) else textMargin = textMargin * ( display.pixelWidth / 320 ) end elseif ( platformName == "Android" ) then textMargin = textMargin * ( system.getInfo( "androidDisplayApproximateDpi" ) / 160 ) end -- Calculate a font size that will best fit the given text field's height local textToMeasure = display.newText( "X", 0, 0, native.systemFont, fontSize ) fontSize = fontSize * ( ( textField.contentHeight - textMargin ) / textToMeasure.contentHeight ) textToMeasure:removeSelf() textToMeasure = nil -- Update the given text field's font size to best fit its current height -- Note that we must convert the font size above for the text field's native units and scale local nativeScaledFontSize = fontSize / display.contentScaleY if ( platformName == "iPhone OS" ) then local modelName = system.getInfo( "model" ) if ( modelName == "iPad" ) or ( modelName == "iPad Simulator" ) then nativeScaledFontSize = nativeScaledFontSize / ( display.pixelWidth / 768 ) else nativeScaledFontSize = nativeScaledFontSize / ( display.pixelWidth / 320 ) end elseif ( platformName == "Android" ) then nativeScaledFontSize = nativeScaledFontSize / ( system.getInfo( "androidDisplayApproximateDpi" ) / 160 ) end return nativeScaledFontSize end |
This function first calculates the text margin, applying both Corona’s content scaling and the device’s DPI-dependent scaling. Next, it creates a temporary text object, measures its height, and computes the unscaled font size based on the height of the text field (minus the margin). Then it calculates the natveScaledFontSize
value, applying the device’s scaling. Finally, it returns this size to the caller.
Remember that to use this function, we must first create the text field and then set the field’s font size based on the returned value of the function call:
1 2 |
local myInputField = native.newTextField( display.contentCenterX, 100, 200, 40 ) myInputField.size = native.getScaledFontSize( myInputField ) |
Gotchas
As always, when dealing with native objects, you should test on as many actual devices as possible. While the Corona Simulator for Mac allows for basic preview of input text fields, the text inside these fields will not be automatically scaled if the Simulator window is zoomed in or out — it will only be displayed at the correct size when the Simulator window is at normal zoom.
Conclusion
Hopefully this tutorial has illustrated some useful tips — and two convenient functions — which can help you better implement native text fields on varying platforms.
Andreas
Posted at 03:02h, 05 DecemberHi Rob,
Thanks a lot, this is just what I needed! Will put it into my code immediately.
Best,
Andreas