In this chapter, we’ll examine TableView basics then dig a bit deeper into what you can do with tables. We’ll look at headers, footers, and sections. Finally, we'll wrap up with a look at handling events associated with tables and rows.


Let's start by creating a table. You do so with the Ti.UI.createTableView() method, like this:

var table = new Titanium.UI.createTableView({
	/* properties */

Some key table properties include:

  • height and width – controls the dimensions of the table; it doesn't have to fill its parent container
  • top and left – controls placement of the table, useful if you want to add buttons or labels above or below it
  • backgroundColor and backgroundImage – controls the background of the table; transparent is a valid backgroundColor
  • rowHeight / minRowHeight / maxRowHeight – controls table-wide dimensions of rows
  • headerTitle / headerView – controls the table's header
  • footerTitle / footerView – controls the table's footer
  • scrollable (boolean) – controls whether the table is scrollable (vertically)

Assigning data to your table

Next we'll move on to adding rows, which are the core of any table component. Let's take a look at the options that Titanium makes available to you for creating and adding rows.

Table rows are represented by the Ti.UI.TableViewRow object. This object contains various properties and methods that you can use to style and manage your table rows. You can create TableViewRow objects explicitly using the Ti.UI.createTableViewRow() function. Conversely, you can use simple Javascript object literals to represent your rows. For quick and dirty row creation, the object literal technique is likely faster and easier. When you want to carefully style and control the behavior of rows, though, you'll probably want to create explicit row objects.

Object literals as rows
// create an array of anonymous objects
var tbl_data = [
	{title:'Row 1'},
	{title:'Row 2'},
	{title:'Row 3'}
// now assign that array to the table's data property to add those objects as rows
var table = Titanium.UI.createTableView({
// alternatively, you could do
var win = Ti.UI.createWindow();


Creating object literals in this way is very handy when pulling data out of a database or across the network. By explicitly creating TableViewRow objects, you gain access to a few handy methods such as add() or fireEvent().

Explicit TableViewRow objects
var row = Titanium.UI.createTableViewRow({
    title: 'Row 1'
    /* other properties */
// with an explicit object, you can call methods such as
// var imgCapture = row.toImage();

Emptying a table

You can empty a table by setting its data property to an empty array.


Do not set a TableView's data property to null or undefined as it will result un unexpected behavior.

// or = [];

Setting data vs. setData() vs. appendRow()

For best performance, create an array of row objects (object literals or explicitly typed) and then assign them to the table using either setData() or by setting the data property. In community tests, appendRow() performs significantly slower than setData() when adding thousands of rows to a test table. Though this is an uncommon scenario, it is still best to manage your tables, and all UI components, in the most performant manner possible.

If your app does however currently require a table with thousands of rows, you should may want to reconsider your UI. Users won't want to scroll through that many rows to find the one that interests them. Even on the fastest device, such a table will be slow. Consider some sort of drill-down interface, filtering mechanism, or alternate UI/UX paradigm to reduce the size of the available table.

Row properties

Now that we've seen how to create tables and rows, let's learn a bit more about the built-in row properties. TableViewRow objects have various useful properties that you can use to add style and functionality to your tables.

  • className – set this property equal to an arbitrary string to optimize rendering performance. On both iOS and Android, setting this property enables the operating system to reuse table rows that are scrolled out of view to speed up the rendering of newly-visible rows. On iOS, the string you supply is used to specify the reuse-identifier string (setdequeueReusableCellWithIdentifier); on Android, it is used within a custom object reuse method within Titanium.
  • leftImage – set this property equal to an image URL (local or remote) to display that image to the left of the row's title
  • rightImage – set this property equal to an image URL (local or remote) to display that image to the right of the row's title
  • backgroundImage – set this property equal to an image URL (local or remote) to display that image in the background of the row
  • backgroundColor – set this property to a color string to set the row's background color

So let's augment the properties of the prior TableView example rows. In the following code we'll utilize the above properties on a per row basis, making for a highly styled set of table rows.

var tbl_data = [
    {title:'Row 1', leftImage: 'KS_nav_ui.png'},
    {title:'Row 2', rightImage: 'KS_nav_ui.png'},
    {title:'Row 3', backgroundColor: '#fdd'}
// now assign that array to the table's data property to add those objects as rows
var table = Titanium.UI.createTableView({
// alternatively, you could do

var win = Ti.UI.createWindow();


Row indicators

Row indicators are icons that provide visual cues to your users related to your table rows. As shown in the following graphic, Android supports two built-in icons while iOS supports three. Each is a boolean value set with the property listed following the graphic.

  • hasChild – indicates sub-table or additional rows (most commonly used on iOS with the navigation controller)
  • hasDetail – indicates a detail view or alert will appear when row is tapped (not supported on Android)
  • hasCheck – an on/off or yes/no indicator

Custom rows

If the stock properties don't suit your needs, you can add Views, ImageViews, Labels, Buttons, and so forth as children of your rows. This gives you enormous flexibility when laying out the content of your table.

Let's once again visit the example TableView code. This time we'll modify it to use explicit TableViewRows. To these TableViewRows we will add a label, button, and image in a custom format.

// Create an array of explicitly defined custom TableViewRows
var tbl_data = [];
for (var i = 0; i < 10; i++) {
	var row = Ti.UI.createTableViewRow();
	var label = Ti.UI.createLabel({
		left: 10,
		text: 'Row ' + (i+1)
	var image = Ti.UI.createImageView({
		image: 'KS_nav_ui.png'
	var button = Ti.UI.createButton({
		right: 10,
		height: 30,
		width: 80,
		title: 'press me'

// now assign that array to the table's data property to add those objects as rows
var table = Titanium.UI.createTableView({

var win = Ti.UI.createWindow();

This is just one very simple example of how you can create custom rows for your tables. You can literally embed almost any Titanium UI component in any visual configuration to create your rows.


It is tempting to make heavy use of Titanium's flexibility with custom rows. You need to be aware, however, of the performance implications of making your rows overly complex. Each unique UI element you add to a row has resources requirements, and those requirements are magnified by the number of rows in your tables. Be sure to test both on simulator/emulator and device as you develop to ensure you are getting the app performance you expect, and scale back row complexity if necessary.

Grouped rows

On iOS, you can set the style property of the table to display table sections as separate components, as shown in the following graphic.

var inputData = [
	{title:'row 1', header:'Header 1'},
	{title:'row 2'},
	{title:'row 3'},
	{title:'row 4', header:'Header 2'},
	{title:'row 5'}
var table = Titanium.UI.createTableView({

var win = Ti.UI.createWindow();

Headers and footers

You can use the built-in headerTitle and footerTitle to add header and footer titles to your tables. This convenience property allows you to enter arbitrary text for these titles, but will only use the default font formatting.

var data = [
	{ title: 'Row 1' },
	{ title: 'Row 2' },
	{ title: 'Row 3' }
var table = Titanium.UI.createTableView ({
    headerTitle:'TableView examples and test cases',
    footerTitle:"Wow. That was cool!",

var win = Ti.UI.createWindow();


A more flexible technique is to use Views for your headers and footers. You can create a view and set it in the table's headerView or footerView properties. Here's an example of setting both the header and footer using these properties.

var tbl_data = [
	{ title: 'Row 1' },
	{ title: 'Row 2' },
	{ title: 'Row 3' }

var createCustomView = function(title) {
	var view = Ti.UI.createView({
		backgroundColor: '#222',
		height: 40
	var text = Ti.UI.createLabel({
		text: title,
		left: 20,
		color: '#fff'
	return view;

// now assign that array to the table's data property to add those objects as rows
var table = Titanium.UI.createTableView({
    headerView: createCustomView('Header View'),
    footerView: createCustomView('Footer View')
// alternatively, you could do

var win = Ti.UI.createWindow();

Table sections

Table sections enable you to create groupings of related rows within your table. Table sections can have headers and footers. You create sections with the Ti.UI.createTableViewSection() method. Then, you add rows to that section and set the table's sections property equal to an array of sections. Like this:

// Create the first TableViewSection
var section1 = Ti.UI.createTableViewSection({
	headerTitle:'Header 1'
// use a loop to add some rows
for (var i=0; i < 4; i++) {
		title:'Row '+i
// do it all again...
var section2 = Ti.UI.createTableViewSection({
	headerTitle: 'Section 2'
for (var i=4; i < 10; i++) {
		title:'Row '+i
// Now, here's our table, and we're setting the data to hold the sections
var table = Ti.UI.createTableView({

var win = Ti.UI.createWindow();

Prior to Release 3.0, you need to use the TableView property data to set the sections.

Adding or removing sections from a table

The information in this section only applies to Release 3.0 and later.

After creating and rendering a TableView, you can add sections using the TableView methods: appendSection, insertSectionBefore and insertSectionAfter. Using insertSectionBefore or insertSectionAfter, you can insert individual sections anywhere in the TableView, but using appendSection, you can only insert individual or multiple sections to the end of the TableView. For the iOS platform, you can optionally choose to render the addition with an animation.


There are several outstanding issues with the APIs described in this section. Until these issues are resolved, these APIs should be avoided.

  • TIMOB-12616 - iOS: TableView Click Events dont get added under certain circumstances
  • TIMOB-12620 - Android: TableView.sections property not supported
  • TIMOB-12625 - iOS: TableView.updateSection argument order is wrong
  • TIMOB-12630 - MobileWeb: TableView click event not fired after updateSection
var tv = Ti.UI.createTableView({

// Render table...

// Inserts a section at the beginning
tv.insertSectionBefore(0, section1);

// Inserts a section in between section2 and section4
tv.insertSectionAfter(1, section3);

// Inserts multiple sections at the end of the table

You can remove individual sections from a TableView using the TableView method, removeSection.

var tv = Ti.UI.createTableView({

// Render table...


Prior to Release 3.0, you need to use the TableView property, data, to refresh the entire table.

Iterating over the rows in a table using Section

Titanium always creates at least one table section for you even if you don't explicitly create a TableViewSection. This is handy because sections have a rows property, which tables do not. This property stores an array of all the rows in that section. In other words, you can use sections to loop programmatically through the rows in a table.

var table = Ti.UI.createTableView({});
// we'll add rows, but not sections = resultsOfSomeDatabaseOperation();
// when the table is clicked, loop through the rows
table.addEventListener('click', function(e){
	var sections =; // grab the array of sections
	var section = sections[0]; // use just the first section
	for(var x=0,y=section.rowCount;x < y;x++) {
		var theRow = section.rows[x];
		// do something with theRow

Searching within a table

You can enable searching by adding a search bar to your tables. As users enter text, rows are filtered such that only those containing the text in their title property remain visible. The search is not a leading character search. In other words, searching for the letter "b" would display all rows containing that letter anywhere within their title.

function makeRow() {
	// generate random string of digits and capital English letters
	// see
	return Ti.UI.createTableViewRow({
		title: Math.random().toString(36).substring(7)
var searchbar = Ti.UI.createSearchBar({
	barColor: '#385292',
	showCancel: false 
var tbl = Ti.UI.createTableView({
	search: searchbar,
	hideSearchOnSelection: true
var data = [];
for(var i=0; i<100; i++) {
} = data;

As you can see in the image below, there are platform differences in the way the search bar is rendered. A couple of the properties used bear further explanation:

  • showCancel if true then show a cancel ("clear") button always. If false on Android never show that button; on iOS don't show Cancel until the user begins typing.
  • hideSearchOnSelection is an iOS only property. With it set to true as soon as the user taps a row in the search results, the search box is cleared and the Cancel button is hidden. Set it to false and the search text and button would remain. The default value is true Android operates as if this value were set to false

Events and event object properties

As shown in the preceding code, you can add event listeners to tables. From there, you can access sections, rows, and nested child elements. While you could add event listeners directly to the rows, we don't recommend it. Doing so adds unnecessary memory and processing overhead to your app. Also, it can be difficult and repetitive adding listeners to all the various rows.

table.addEventListener('click', function(e){
	alert('You clicked row '+e.index);

As shown in the code above, you have access to an event object, e, that holds important table information within the listener. For example, some of the key properties of that event object include:

  • index – the ordinal index number of the row that received the event
  • row – the object representing the row that received the event
  • rowData – the properties of the row that received the event
  • source – the object that received the original event
  • section – the table section that received the event

Scroll events

For the iOS and Mobile Web platforms, the scroll events, scroll and scrollend, have different key properties than the other table view events:

  • contentOffset – point that indicates how far the content in the table has moved. Since a table view can only scroll vertically, only the y-coordinate changes. A positive value indicates scrolling upward and a negative value indicates scrolling downward. See image below for a diagram of the content offset property.
  • contentSize – dimensions of the content, for example, the content height of a table with a few rows is smaller than the visible height. As illustrated in the image below, the pink box indicates the contentSize property and the blue box indicates the size property.
  • size – visible dimensions of the table view.

Pull to refresh

Since Release 2.1 on iOS, you can create a custom view that is only revealed when the user pulls the table down.  This custom view is commonly used to implement a pull-to-refresh feature that updates the table data when the user drags the table downward.  Create a custom View object containing other view objects, then set the TableView's headerPullView to this object. Monitor the scrolling event to see when the user pulls the table downward to initiate an update to the table.

While this example provides all the logic necessary to create the control, it is recommended to refactor it as a CommonJS module before using it for a production application.

var win = Ti.UI.createWindow({fullscreen: true});
function getFormattedDate(){
    var date = new Date();
    return date.toLocaleString();
var tableHeader = Ti.UI.createView({
    width:320, height:80
var border = Ti.UI.createView({
var imageArrow = Ti.UI.createImageView({
    left:20, bottom:10,
    width:23, height:60
var labelStatus = Ti.UI.createLabel({
    font:{fontSize:13, fontWeight:'bold'},
    text:'Pull down to refresh...',
    left:55, bottom:45,
var labelLastUpdated = Ti.UI.createLabel({
    text:'Last Updated: ' + getFormattedDate(),
    left:55, bottom:15,
var actInd = Ti.UI.createActivityIndicator({
    left:20, bottom:13,
    width:30, height:30
var tableView = Ti.UI.createTableView({

var tableRowTotal = 0;

function loadTableData(table, count, callback){
    for (var i=tableRowTotal, ilen=tableRowTotal+count; i<ilen; i++){
        var rowID = i + 1;
        table.appendRow({title:'Row ' + rowID});
    if(callback && typeof callback === "function"){
loadTableData(tableView, 5);
var pulling = false;
var reloading = false;
var offset = 0;
    offset = e.contentOffset.y;
    if (pulling && !reloading && offset > -80 && offset < 0){
        pulling = false;
        var unrotate = Ti.UI.create2DMatrix();
        imageArrow.animate({transform:unrotate, duration:180});
        labelStatus.text = 'Pull down to refresh...';
    } else if (!pulling && !reloading && offset < -80){
        pulling = true;
        var rotate = Ti.UI.create2DMatrix().rotate(180);
        imageArrow.animate({transform:rotate, duration:180});
        labelStatus.text = 'Release to refresh...';
function resetPullHeader(table){
    reloading = false;
    labelLastUpdated.text = 'Last Updated: ' + getFormattedDate();
    labelStatus.text = 'Pull down to refresh...';
    table.setContentInsets({top:0}, {animated:true});
    if (pulling && !reloading && offset < -80){
        pulling = false;
        reloading = true;
        labelStatus.text = 'Updating...';
        e.source.setContentInsets({top:80}, {animated:true});
            loadTableData(e.source, 5, resetPullHeader(e.source));
        }, 2000);

Hands-on practice


In this activity, you will create a custom table that doesn’t fill the entire viewport. The table will contain customized rows with background images that differ based on the row’s location within the table. Each row will contain two images and two labels. When you tap a row, an event listener will determine if either of the images was the object that received the tap. If so, that image will be swapped with an alternate graphic.

When completed, your app should match what is shown in this movie:


Download the starting point code from This archive includes the necessary graphics.


  1. Download, extract, and then import the TableView project into Studio. Confirm that the tiapp.xml file has appropriate values then close that file. Open app.js in Studio.
  2. Following the comments included in the starting app.js file, add these elements to the app:
    • Set the window background to images/gradientBackground.png
    • Add a page heading of “Custom Table” with a dark blue, 18 px, bold font that is positioned at the top-left of the window
    • Define a table that is positioned below the label and which is 90% of the width of the screen and 85% of its height. Set the table’s background color to transparent and for iOS, set the separator style to NONE.
  3. Write a function named makeRow() following the specifications included in the comments within the code.
    • Your makeRow() function should accept an object that will be used to pass in four values: the row number, primary label text, secondary label text, and a custom “which image” indicator string. Possible values for the row number parameter will be an integer or the string ‘last’. Possible values for the myImage string property will be a, b, c, blue, and red. You’ll use this myImage property to determine which element in the row is tapped and to swap images accordingly.
    • Each row should contain two images:
      • Left image: if an even-numbered row = ‘images/imageA.png’ otherwise ‘images/imageB.png’. It should have a custom property named myImage that is set equal to the myImage parameter passed to your makeRow() function at run time.
      • Right image: use ‘images/notificationBadge.png’ and set its custom myImage property equal to ‘blue’
    • Each row should contain two labels:
      • Primary label: use a bold, 16 px font positioned to the right of the left image with its text set equal to the primary label parameter passed to makeRow() at run time. Be sure to set a height for the label.
      • Secondary label: use a bold, 13 px font positioned below the primary label with its text set equal to the secondary label parameter passed to makeRow() at run time. Be sure to set a height for the label.
    • Each row’s background image should be set to ‘images/middleRow.png’ and the selected background image to ‘images/middleRowSelected.png’. If the row number parameter is 0, set the row’s background image to ‘images/topRow.png’ and selected background image to ‘images/topRowSelected.png’. If the row number parameter equals ‘last’ then use ‘images/bottomRow.png’ and ‘images/bottomRowSelected.png’ for the background images and use ‘images/imageC.png’ for the left image.
    • The makeRow() function should return a Titanium.UI.TableViewRow object.
  4. Use a for-loop to create an array of 8 rows for your table, calling makeRow() in each iteration of the loop. Set the primary label to “This is row” plus the row-number indicator. Set the secondary label to “Subtitle” and the row-number indicator. Set the myImage value to either ‘a’ or ‘b’ depending on whether your loop counter is odd or even. (Hint: use the modulus operator, %, to calculate this odd/even value.)
    Push one additional row into the rows array. This row should pass these values:
    • row number: ‘last’
    • primary label: ‘This is the last row’
    • secondary label: ‘The last subtitle’
    • myImage: ‘c’
  5. Add a click event listener to your table. Check the myImage property of the event source. Using a switch or if-else test, determine if myImage is set equal to a, b, c, blue, or red. If a, b, or c, swap the left image such that imageA becomes imageB, imageB becomes imageC, and imageC becomes imageA again. If the myImage property equals blue, the right image should change to images/notificationUnreadBadge.png otherwise it should swap back to images/notificationBadge.png.
  6. Save and run the project in the simulator. Confirm that the user interface matches the screenshots shown below. Confirm that your event listener functions properly:
    • When the row is tapped, the background image should swap to the green “selected” version.
    • If the left image is tapped, the letter should advance to the next letter, looping back to the beginning properly.
    • If the right image is tapped, it should swap between the red and blue versions properly.

References and further reading


In this section, you reviewed TableView basics, then explored some of its more powerful functionality. We looked at headers, footers, and sections. Finally, we wrapped up with a look at handling events associated with tables and rows. In the next section, we'll see how we can add custom icons and splash screens to our Titanium apps.

Related Links