If you missed either of the previous Widgets 2.0 tutorials, please read them to learn about the additional new widgets.
- New Widgets: Part 1 — switch and segmented control.
- New Widgets: Part 2 — button, tab bar, slider, stepper and spinner.
Today’s tutorial explains the remainder of the new Corona widgets. These include the following, available in the latest Daily Builds.
- table view — allows you to create scrolling lists of data.
- scroll view — allows you to create scrolling content areas.
- picker wheel — supports custom columns and extraction of values from rows within the “selection area.”
- progress view — an incrementing “fill bar” to indicate what percentage of a task is complete.
Important!
Please refer to the current Daily Build documentation as you experiment with new widgets. Several things have been changed or renamed from the old widgets, so you should study the documentation carefully. To assist you, we have included a migration guide to help you make the change. From the linked API page, scroll down to the widget.* section. You’ll find the updated references and migration guide there.
Getting Started
As with several other Corona libraries, you must “require” the widget library to use widgets:
local widget = require( "widget" )
Table View
The table view widget is a scrollable view of table rows with “momentum scrolling” and the ability to set particular rows as “categories” that will lock in place when they reach the top of the widget view. The 2.0 version of the table view also features new customization options including the friction and max velocity of the scroll behavior.
Event Listener
In contrast to other widgets, the table view can accept four event listeners, each used for a particular purpose.
1. Movement Listener
Used to listen for movement-based table view events, with the following events:
- event.limitReached indicates that the table view has reached one of its limits.
- event.direction returns the direction the table view is moving in.
local function tableViewListener( event ) print( event.direction ) print( event.isLimitReached ) end
2. onRowRender
Used to listen for table view row rendering events with event types of rowRender. This listener is only initiated on initial rendering of your table view rows. In your listener, event.row is a reference to the table view row that was rendered. The following example auto-fills each row with a text label that matches the row number, centered vertically in the row.
local function onRowRender( event ) local phase = event.phase local row = event.row local rowTitle = display.newText( row, "Row " .. row.index, 0, 0, nil, 14 ) rowTitle.x = row.x - ( row.contentWidth * 0.5 ) + ( rowTitle.contentWidth * 0.5 ) rowTitle.y = row.contentHeight * 0.5 rowTitle:setTextColor( 0, 0, 0 ) end
3. onRowUpdate
Used to listen for table view row rendering events, with event types of rowUpdate. This listener is only initiated when previously off screen rows become visible again. In your listener, event.row is a reference to the table view row that was made visible. The following example returns a response when a particular row enters the screen.
local function onRowUpdate( event ) local phase = event.phase local row = event.row print( row.index, ": is now onscreen" ) end
4. onRowTouch
Used to listen for table view, with event phases of press, release, swipeLeft, and swipeRight. In your listener, event.row is a reference to the table view row that you interacted with. Note that the table view press phase is triggered after a very short touch-and-hold on the row. This is by design to prevent the row from considering a momentary or accidental “flick” tap event as a “press” on that row.
local function onRowTouch( event )
local phase = event.phase
if ( "press" == phase ) then
print( "Touched row:", event.target.index )
end
end
Standard Table View
Declaring a new table view is accomplished as follows:
local tableView = widget.newTableView
{
left = 0,
top = 52,
width = 320,
height = 366,
maskFile = "assets/mask-320x366.png",
listener = tableViewListener,
onRowRender = onRowRender,
onRowUpdate = onRowUpdate,
onRowTouch = onRowTouch,
maxVelocity = 1.0,
friction = 0.972,
noLines = false,
}
- left and top (optional) — the position where the table view will be created. The default values for left and top are 0, 0.
- width and height (required) — width and height of the table view. At this time, these parameters only control the “behavioral bounds” of the table view, not the visual boundaries. In other words, the height parameter specifies the “internal height” of the table view in which rows are considered to enter and exit the view. If you wish to create a table view that visually spans just a portion of the screen, you must define the proper width and height parameters, create a matching bitmap mask, and apply it using the maskFile parameter.
- maskFile (optional) — if you set a custom width or height, you should specify the maskFile parameter, which is the filename of the bitmap mask. This mask should mimic the defined width and height.
- listener, onRowRender, onRowUpdate, onRowTouch (optional) — the table view’s corresponding event listener functions, according to each type discussed above.
- maxVelocity (optional) — use this to limit the maximum scrolling speed of the table view widget. The default value is 2.
- friction (optional) — this determines how fast the view moves when it’s “flicked” up or down. The default value is 0.972, which should be sufficient for most cases. In previous versions of this widget, you were not able to set this property. Now, you can set the “flick speed” faster or slower. Set to 0.0 to effectively turn off “momentum scrolling.”
- noLines (optional) — set to true to remove the lines from around each row.
Next, you must create rows in the table view. This is done via the tableView:insertRow() function, with four parameters.
tableView:insertRow
{
isCategory = false,
rowHeight = 24,
rowColor = { default={ 255, 255, 255 }, over={ 0, 175, 250 } },
lineColor = { 150, 150, 150, 50 },
}
- isCategory — sets a particular row as a category. This means that the row cannot be clicked on, and when it reaches the top of the view, it will “lock” in place as other rows scroll beneath it, and “unlock” when the view is scrolled back down.
- rowHeight — integer to set the height of a particular row.
- rowColor — consists of two sub-tables: default and over, each being a table of RGB + alpha values for the row background color. The default color is the row background color in the normal state, while over is the highlight color when the row is pressed.
- lineColor — a table of RGB + alpha values for the row border color.
Scroll View
The scroll view widget is a scrollable area larger than the screen size — a large image, for example — with “momentum scrolling.” The 2.0 version of the scroll view features new customization options including the friction in which a screen “flick” will affect the scroll speed.
Event Listener
The event listener for a scroll view is simple. It reveals the usual phases of began, moved, and ended, and additionally it reveals the event.limitReached value which is triggered when the scroll view reaches one of the four directional limits.
local function scrollListener( event )
local phase = event.phase
print( phase )
local direction = event.direction
-- If the scrollView has reached it's scroll limit
if ( event.limitReached ) then
if ( "up" == direction ) then
print( "Reached Top Limit" )
elseif ( "down" == direction ) then
print( "Reached Bottom Limit" )
elseif ( "left" == direction ) then
print( "Reached Left Limit" )
elseif ( "right" == direction ) then
print( "Reached Right Limit" )
end
end
return true
end
Standard Scroll View
The first step is to create the “base” for the scroll view. This is done by setting the basic position and size parameters, plus the total allowed scrollWidth and scrollHeight that the view can scroll.
local scrollView = widget.newScrollView
{
left = 10,
top = 100,
width = 300,
height = 350,
maskFile = "assets/scrollViewMask-350.png",
scrollWidth = 465,
scrollHeight = 670,
friction = 0.972,
listener = scrollListener,
}
- left and top (optional) — the left and top position of the scroll view. You can also position it normally by setting its x and y position afterward.
- width and height (required) — the width/height of the scroll view, in relation to the total scroll distances. Note that these values do not trim (mask) the scroll view to this size on the screen; they simply determine the width and height that the view will scroll until it stops and “springs back” on reaching a directional limit. For example, if your scrollWidth is 1000, and the width is 800, the view can be scrolled for 200 pixels. If you change the width value to 100, the view can be scrolled 900 pixels. Remember that if you’re working with a scroll view that is smaller than the screen size, you need to create a bitmap mask and specify it as the maskFile parameter, similar to the table view masking procedure.
- maskFile (optional) — if you set a custom width or height, you should specify the maskFile parameter, which is the filename of the bitmap mask. This mask should mimic the defined width and height.
- scrollWidth and scrollHeight (required) — these are the width and height of the total scrollable content area. For example, if you place visual content of 1000 pixels wide inside the scroll view, you should set the scrollWidth to 1000 as well. IMPORTANT: this value can not be changed after the scroll view has been created!
- friction (optional) — this determines how fast the view moves when it’s “flicked” up or down. The default value is 0.972, which should be sufficient for most cases. In previous versions of this widget, you were not able to set this property. Now, you can set the “flick speed” faster or slower. Set to 0.0 to effectively turn off “momentum scrolling.”
- listener (optional) — the name of the slider’s event listener function.
Adding Content to a Scroll View — or Other Widgets!
In scroll view 2.0, in addition to the usual placement of visual content inside the view, you can now insert other widgets and achieve the expected behavior. Because the scroll view is a display group itself, you simply add these items to the scroll view (or remove them) as you would when dealing with any other display object and group.
--create a image and add it to the scroll view local background = display.newImageRect( "assets/scrollimage2.jpg", 768, 1024 ) background.x = bg.contentWidth * 0.5 background.y = bg.contentHeight * 0.5 scrollView:insert( background )
--create a "checkbox" widget and add it to the scroll view
local checkboxButton = widget.newSwitch
{
left = 130,
top = 200,
style = "checkbox",
id = "Checkbox button",
onPress = onSwitchPress,
}
scrollView:insert( checkboxButton )
Picker Wheel
The picker wheel mimics the iOS picker wheel, for example the built-in time selector for the “alarm clock” app. You can build the wheel from a flexible set of columns, with custom data for each column and a default starting index for each column.
Configuring the Columns
In this tutorial, we’ll configure a simple date selector wheel. First, let’s populate three column tables: months, days, and years.
local months = { "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" }
local days = {}
for i = 1, 31 do
days[i] = i
end
local years = {}
for i = 1, 44 do
years[i] = 1969 + i
end
Declare the “columnData” Table
Next, we’ll fill the columnData table. This is simply a table with a sequence of sub-tables — those we configured above — and some parameters which will dictate the visual appearance of the columns within the picker wheel.
local columnData =
{
--months column
{
align = "right",
width = 150,
startIndex = 5,
labels = months
},
--days column
{
align = "left",
width = 60,
startIndex = 18,
labels = days
},
--years column
{
align = "center",
width = 80,
startIndex = 10,
labels = years
}
}
Notice that you must declare the columns in the left-to-right order that they should be displayed within the picker wheel, and each column contains the following parameters:
- align (optional) — alignment of the text elements within the column. Default is center alignment.
- width (optional) — specific width of the column. By default, columns are split evenly between the picker wheel’s viewable area.
- startIndex (optional) — the starting index of the column. Must be set to an index within the allowed range, i.e. 1 to 12 for the months column.
- labels (required) — the table of column data to populate the column with.
Declare the Picker Wheel
The final task is to declare the actual picker wheel. The code is essentially as follows:
local pickerWheel = widget.newPickerWheel
{
left = 0,
top = 210,
font = native.systemFontBold,
fontSize = 10,
columns = columnData
}
- left and top (optional) — the left and top position of the picker wheel. You can also position it normally by setting its x and y position afterward.
- font (optional) — the font that will be used when rendering column rows. Default is “native.systemFontBold”.
- fontSize (optional) — the font size for the picker wheel columns. Default is 22.
- columns (required) — the columnData table that we configured above, with the three associated columns of months, days, and years.
Picker Wheel “:getValues()” Function
Reading values from the picker wheel is simple using the :getValues() function. This can be executed programmatically at any point, for example on the press of a “save” button or similar.
local values = pickerWheel:getValues() print( "Column 1: " .. values[1].value ) print( "Column 2: " .. values[2].value ) print( "Column 3: " .. values[3].value )
As you can see, each column is returned in sequential order in the values table, and you access the specific value from that column using the .value property.
Progress View
The progress view is a basic left-to-right incrementing progress bar, useful for asynchronous processes that you queue up within the app. One example is a collection of photos downloading one by one — hence asynchronously — in which you can use the progress view to indicate the percentage of the total process completed.
Standard Progress View
Since the progress view is not an interactive widget, you do not need a listener. Instead, just declare it as follows:
local newProgressView = widget.newProgressView
{
left = 20,
top = 240,
width = 200,
isAnimated = true,
fillXOffset = 0,
fillYOffset = 0
}
- left and top (optional) — the left and top position of the progress view. You can also position it normally by setting its x and y position afterward.
- width (optional) — the overall width of the progress view.
- isAnimated (optional) — set to true if the progress change should be animated, false (or omitted) if the progress change should happen immediately.
- fillXOffset and fillYOffset (optional) — Set these if you wish to position your progress view’s “fill” image at a horizontal or vertical offset. Default is 0. Note that fillXOffset applies to both sides of the progress bar; thus if you set the value to 20, the fill bar be offset to the right by 20 pixels and it will stop 20 pixels to the left of the total distance.
Progress View “:setProgress()” Function
Incrementing or setting the progress view fill bar is accomplished via the :setProgress() function, with a decimal value representing the percentage 0.0 (0%) to 1.0 (100%). The following example increments the progress bar in five even increments spaced across 10 seconds:
local inc = 0.0 local function progressIncrement() inc = inc + 0.2 newProgressView:setProgress( inc ) end timer.performWithDelay( 2000, progressIncrement, 5 )
Wrapping Up…
This tutorial concludes discussion of the current Widgets 2.0. We encourage you to download the latest Daily Build to get the most current versions of each widget. Remember, Daily Builds are a subscriber-only advantage, so please subscribe now to gain access to the latest Corona features and additions.




Carlos
Thank you for this tutorial series. This is the best explanation I have seen for the Widget library.
Mitaten
How big is the performance difference between the new tableView and the old one? In terms of having _several_ rows inside it containing lots of display objects in each row? Is the new tableView generally better compared to the old one?
Nevertheless great blog post
Hector
Thanks for these tutorials, however, I noticed a couple of potential bugs you might want to check:
1.- “TabBar Widget”: I tested the TabBar and WidgetDemo sample codes and I noticed that the background of the tabBar from the sample code looks weird, no matter what background image I have, I can’t make it look right. I also followed the tutorial No. 2 and got the same result as the sample code.
2.- “Scroll View Widget”: I also tested the sample code, and apparently even though the parameter “horizontalScrollingDisabled” is set to true, I can still scroll horizontally. Not sure if it has to do with the length of the “lotsOfText” variable from the sample code.
3.- “Picker Wheel Widget”: I tested the code from both this tutorial and the one in the documentation for build 1041, and I get the following Runtime error:
?:0: attempt to compare nil with number
message
stack traceback:
[C]: ?
?: in function
?: in function
I apologize in advance as it might be an error on my side for not following the tutorial correctly, but these are the errors I got while testing the new widgets
Regards
Hector
Brent Sorrentino
Hi Hector,
Thanks for the notes. The issues #2 and #3 that you report are known regression bugs in Build #1041, but should be resolved in #1042, to be released very soon. Issue #1 might be in the same category, but I’ll check it out. I apologize for the inconvenience.
danny
Hey Hector.
#2 is that there is no parameter named “horizontalScrollingDisabled” The parameter is named “horizontalScrollDisabled”
Issues #1 & #3 are fixed and should be available in the next daily build.
Thanks
Francisco
“This tutorial concludes discussion of the current Widgets 2.0″
I’m still looking forward to read the tutorial about customizing the appearance of the new widgets
Brent Sorrentino
Hi Francisco,
I chose the wrong words there… a theme guide will be created to help developers along, but we want to focus on squashing these pesky bugs first, and ironing out the usability/features issues first. Thanks for your understanding.
Francisco
Great to hear that!
Chevol
+1
I am looking forward to the customization of the new widgets too.
Chevol
Brent The issues #2 and #3 are still present in build #1043! We have seen no progress on the widgets 2.0 or their bugs since Sat and the #1041 build. This has been wrecking havoc on my dev timeline. I’m now having to comment out all my newPickerWheels just so I can run code and develop again. There area too many little bugs still lingering around in these Widgets 2.0 to make them really usable. Where is the tableview row.rerender? What is the workaround/new way to do that? I appreciate all you guys hard work, I’m just worried about these new widgets becoming like the old ones and having absolutely no support with various issues/bugs.
Brent Sorrentino
Hi Chevol,
Let me check into this and figure out when those fixes can be expected to be made public. It should be any day now, sincerely.
I looked at the build notes for #1043 and it appears the fixes did not yet roll into that build, hence the same behavior.
Also, the new widgets will not be allowed to wither on the vine. There will probably be some feature requests that take longer than others, especially if they apply to less common usage and “edge cases,” but in regards to a standard core base, we’re committed to making this happen. With a major revamp of an entire library like this, the growing pains and conversion issues are an unfortunate side effect. I appreciate your patience (and everybody’s patience), and also for noting these issues here and in the forums… we’re receiving all of them and taking the necessary steps as fast as we can.
danny
Hey Chevol.
If you look at the amount of widget 2.0 bugs that have been fixed since they were released you should be re-assured. Having “No support” isn’t the case. All bugs reported are evaluated and fixed in a priority basis.
Some bugs just take longer to fix than others.
We are 100% committed to resolving any issues with widgets 2.0 as fast as possible and will not stop until we reach that point.
Thanks
Sam Tannen
Will it be possible to make a picker wheel out of rows instead of columns? This would be useful if you were populating the wheels with longer strings of text. Also, it would be great if you could use images instead of text.
Craig
Hey Brent. Would you be able to check into the TabBar widget object:setSelected( buttonIndex, simulatePress ) method. The simulatePress parm appears to not be working. It won’t trigger the event even when true. Thanks
Jack01
I am still using Widget 1.0 for the scrollView. It does not have the need for the new fixed scrollHeight. This allows a scrollView to grow, for example when used as a chat application.
danny
The new scrollView has support for dynamic scrollHeight sizing. It does this under the hood. That issue was fixed in daily build 1040
Olivier
Hi,
What about the content property of scrollVIew widget, which is removed on the 2.0 version ?
http://docs.coronalabs.com/api/type/ScrollViewWidget/content.html
I was using this content property to move the content manually without moving the scrollView itself, which was very practical…
How could I do that now?
Thanks for your help
Sincerely,
Olivier
Olivier
I believed that scrollView:scrollTo() would do the job,
http://docs.coronalabs.com/daily/api/type/ScrollViewWidget/scrollTo.html
but we only can scroll to constant position top, bottom, left, right.
Thanks for your help
Sincerely,
Olivier
danny
You can also use
scrollView:scrollToPosition()
That should do what your are after.
Olivier
Thank you Danny, U right…
Sorry
Best
Olivier
Olivier
Hi,
Is there a bug with “numChildren” property on ScrollView widget with widget 2.0 ?
I inserted a lot of display objects on my ScrollView, and then I printed the srcollView.numChildren on terminal, it always says “2″
Thanks
Olivier
Jyrki
Hey,
I downloaded daily build 2013.1050 and tried to migrate my app to use the new ScrollViewWidget. My view items have, however, touch listeners bound to them, and with widget-v1 I used to call scrollView:takeFocus(event) if event.phase was “moved”. New ScrollViewWidget does not seem to have that method, though. So how this should be done with new widget library?
DeadpanDodo
Same issue as Jyrki, the takeFocus is gone making the new scrollView useless.
Going back to an old build, … as usual since 1028.
lessmsios
Nice tutorial – and good to see that Corona is making headway on widgets.
In using the Table View it would really be nice if the width and height controlled the visual view as well. I’m working on an app that is size-dynamic using percentages of areas of the screen for the layout. Table View works fine except parts of the bottom rows remain out of view. I know there is the maskFile option but to have such a file for ever possible device is not practical.
Do you see the width/height controlling both the “behavioral bounds” of the table view AND the visual boundaries in the near future?
Thanks in advance.
lessmsios
Ignore my post – I figured it out.
frank
Brent, do you have a timeline by when the widget issues are fixed ?
Brent Sorrentino
Hi Frank,
The widget issues are being steadily fixed and addressed as they come into the queue, and fixes rolled out in daily builds. Do you have a specific issue which I should check on the status of?
Alberto
Same issue as Jyrki and DeadpanDodo. How can now get that featured?
Now my scrollview is not working
I do everything in the Migration guide, I get no errors but the buttons in my scrollview doesn’t work.
Ross
Alberto, Mine is not either. I must not know how to migrate from Widget 1.0 to 2.0.
I get no scroll action with 2.0. Going to try to go to an earlier non 2.0 version.
Peter
Brent – Does anyone know when a tutorial for customizing widget appearances will be published? I’d love to learn how to do that with the new widget library…
Brent Sorrentino
Hi Peter,
The skinning guide is already underway, but we need to make sure it covers all bases and widgets, and is very clear on the use-cases that we imagine developers will commonly need. Thanks for your patience, we know this has been requested by several users.
Brent
frank
Brent,
regarding the tableView, isn’t your sample code of onRowRender something ? How is the group populated. In widget 1.0, we used to set the event.view. How is this done in 2.0 and where is the change documented ?
Thanks
Frank
Example from 2.0
local group = event.view
local row = event.target
local text = display.newText( “My first TableView row!”, 0, 0, native.systemFont, 18 )
text:setReferencePoint( display.CenterLeftReferencePoint )
text.x = 25
text.y = row.height * 0.5
— you must insert any display objects into event.view group
group:insert( text )
end
Simon Fearby
Is everyone else having to hold down the press a for a at leave 1000ms to get the “release” to fire in the onRowTouch( event ) widget.newTableView
Brent Sorrentino
Hi Simon,
The docs haven’t been updated (yet), but the row touch (in about daily build 1080 and above) now features the following events:
tap, press, release, swipeLeft, swipeRight, cancelled
The “tap” being what you’d expect. As for the press/release, you’ll be required to “hold down” for a very short amount of time, but certainly not 1000 ms…. maybe 250 ms or so (I can’t remember the exact value but it’s around there).
Brent
Jane Scarano
Can we add a background image to the tableView? If yes, how do we do this?