Highlighting selected row of table view

Edit: I made it work by adding a call to buildingTableView.reloadData() after changing the color of the old row.  However, now sometimes when I drag the table view up or down I get the following error:

20:20:32.377 ERROR: Runtime error 20:20:32.377 ?:0: attempt to call method 'setFillColor' (a nil value) 20:20:32.377 stack traceback: 20:20:32.377 ?: in function 'touch' 20:20:32.377 ?: in function '?' 20:20:32.377 ?: in function \<?:182\>

This happens even with all calls to setFillColor commented out in my code.


Okay, I’m trying to work out how to use table views and I’m either making a dumb mistake or having a fundamental misunderstanding of how what I’m trying to do works.  What I want to do is to have the last row tapped in my table view have a different background color than the other rows to indicate that it’s currently selected.  For this I have the following row touch listener:

local function onBuildingRowTouch(event) local row = event.target local params = row.params if event.phase == "tap" or event.phase == "press" then --change row colors if oldBuildingRow ~= nil and oldBuildingRow.index ~= row.index then --print("Old row index: "..oldBuildingRow.index) oldBuildingRow:setRowColor({default = backgroundColor, over = overColor}) end row:setRowColor({default = selectedColor, over = overColor}) oldBuildingRow = row end end

where oldBuildingRow is a file-level variable.  What’s supposed to happen is that the old highlighted row, if there is one, is reset to the default backgroundColor. Then, the newly pressed row is highlighted with selectedColor.  Finally I store a reference to the current row in oldBuildingRow for the next time the listener is called. 

What actually happens is that a given row does not un-highlight when another row is selected.  It’s as if the reference to the old row is no longer “active,” even though the index and all the params and everything can still be accessed from the oldBuildingRow variable.

Does anybody know what I’m doing wrong?

Also, is there any way to distinguish a tap event from a drag?  Currently the above triggers when I drag to move the table view up and down (the row where I begin the drag is selected).  Ideally I would like it to trigger only when the user touches the row and does not drag afterwards.

Thanks!

Hi @Sylnox,

Conceptually this can be a bit tricky at first, so I’ll try to explain it without boring you too much. :slight_smile:

Basically, picture a specific TableView row as a dedicated “unit” (technically a Lua table at its core) while it exists on screen. When the row is rendered by your “onRowRender” function (when you insert a row), the row is designed as you make it, with whatever visual elements you place inside it.

Anyway, while that row exists on screen, you could use a reference/pointer to it and make visual changes to it. But once it goes far enough off screen, that “unit” is effectively destroyed (that’s a core part of the memory management aspect of TableView, to allow you to have hundreds of rows populate the view without keeping them all in memory at once). Basically, once the row is destroyed, any direct reference you stored for it will become invalid (because the Lua table ID reference died)… in your case, that means that “oldBuildingRow” will point to a non-existent object (and thus your attempts to change aspects of the row later will fail).

Now, let’s assume you scroll back to where you were (in the TableView). The row that was destroyed will come back into view, as expected, but it won’t be the same reference ID because it has been re-rendered as a new unit. That’s why your pointer for “oldBuildingRow” fails, because even though the row appears the same to you, it’s not the same ID internally from Corona’s standpoint.

You basically have the correct idea for keeping reference to the index of whatever row is “currently selected” or whatever. Where you tripped up slightly is by setting it as property of a row unit (“oldBuildingRow.index”) which, as I mention above, is not permanent and reliable because the row references are always changing as the rows appear/disappear.

Basic solution is this: store one reference to “oldBuildingRow” somewhere above/outisde the TableView code (to follow proper Lua scope rules). But do not set its value to a row’s reference (ID)… instead, set it to the direct and specific  index of the row. Then, in your “onRowRender” function, conditionally check if that row index matches the index value of “oldBuildingRow”… if it does, take whatever visual actions you want to make that row appear “selected” or whatever.

The other step is, you’ll need to re-render (:reloadData() API) the TableView in your “onBuildingRowTouch()” function, immediately after setting the “oldBuildingRow” variable to whatever row was just touched/selected. That API will basically loop through the on-screen visible rows and, because you added the conditional check in the previous paragraph, it will mark the proper row as selected while resetting the others.

Whew… that was a bunch of stuff to write. It’s probably more confusing than I intended it to be, so please just ask if something doesn’t make sense here.

Best regards.

Brent

Thanks for the lengthy explanation!  It sounds like the only reason my code is working as it stands may be that I haven’t been testing with enough data to have my table view rows destroyed as I drag the table around.  I will have more data soon, so I will be able to see if it works with more rows.  I’m guessing I will have to make the changes you recommended to make it work. 

You don’t happen to have any idea about that error message, do you?  It doesn’t look like most messages I’ve gotten (they usually give me a file and line number where things are going wrong, but that one doesn’t) and it happens even though the only instances of object:setFillColor in my code are protected by a

if object.setFillColor then object:setFillColor(color) end

structure (or even if the lines are commented out entirely).

That error looks familiar to me… where in the overall flow of things are you calling “reloadData()”?

See below for where I’m calling it…

Okay, now I have this:

local currentClassroomRowIndex local function onClassroomRowRender(event) local row = event.row local params = event.row.params local rowHeight = row.contentHeight local rowWidth = row.contentWidth print("Rendering row "..row.index) if row.index == currentClassroomRowIndex then print("ROW IS SELECTED") row:setRowColor({default = theme.selectedColor, over = theme.overColor}) else row:setRowColor({default = theme.backgroundColor, over = theme.overColor}) end local rowTitle = display.newText(row, params.name, 20, rowHeight \* 0.5, theme.font, 16) if rowTitle.setFillColor then rowTitle:setFillColor(theme.textColor) end rowTitle.anchorX = 0 end local function onClassroomRowTouch(event) local row = event.target local params = row.params if event.phase == "tap" or event.phase == "press" then currentClassroomRowIndex = row.index print("touched row "..row.index) classroomTableView:reloadData() end goButton.isVisible = true end

The print statements all print what you would expect. Result: the row just below the one I tap is highlighted with theme.selectedColor.  What the heck?

edit

Do you have the latest public release of Corona (3135) that was released a couple days ago? If so, I can point you to an updated sample which you could literally copy the code from to solve this (because I just updated it).

Brent

Okay, I just updated.  A sample would be great…I’m trying everything I can think of and it’s just not working for me.

edit:

I’m becoming strongly of the opinion that something is screwy with Corona here.  If my row render function looks like this:

local function onClassroomRowRender(event) local row = event.row local params = event.row.params local rowHeight = row.contentHeight local rowWidth = row.contentWidth local rowTitle = display.newText(row, params.name, 20, rowHeight \* 0.5, theme.font, 16) if rowTitle.setFillColor then rowTitle:setFillColor(theme.textColor) end rowTitle.anchorX = 0 if currentClassroomRowIndex and row.index == (currentClassroomRowIndex) then row:setRowColor({default = theme.selectedColor, over = theme.overColor}) local checkMark = display.newImageRect( row, "res/checkmark.png", 30, 30 ) checkMark.x = rowWidth - 30 checkMark.y = rowTitle.y else row:setRowColor({default = theme.backgroundColor, over = theme.overColor}) end end

the result is that the checkmark image is placed in the row I press but the row below that is highlighted.  I may give up on highlighting and just indicate the selected row with the checkmark.

OK, check out the “SampleCode” folder in your Corona application folder. Then go to “Graphics > FilterViewer”. See if that is the basic concept you’re trying to achieve, where (in this sample) you select a filter and the title of the row changes color… and then no matter if you scroll it way off the view or not, it remains that color.

This sample also utlizes what I feel is a more natural user experience, where the rows are highlighted on “tap” without actually using the “tap” listener for the TableView, but rather a combination of “press” and “release”.

If this sample would serve your model, I can help walk you through any of the code questions you have on it (there are, of course, big sections which you can outright ignore, but then replace with your own code to fit your app).

Brent

Okay, thanks - I didn’t realize all that sample code was there!  I ended up using the checkmark to indicate a selected row and selecting a row on release events since it works perfectly and doesn’t crash, but I may take a look at that code later just to see how it differs from what I had originally.

Good to hear it worked for you! I know TableViews can be a somewhat lofty setup process, but hopefully the sample got you going in the right direction.

Brent

Hi @Sylnox,

Conceptually this can be a bit tricky at first, so I’ll try to explain it without boring you too much. :slight_smile:

Basically, picture a specific TableView row as a dedicated “unit” (technically a Lua table at its core) while it exists on screen. When the row is rendered by your “onRowRender” function (when you insert a row), the row is designed as you make it, with whatever visual elements you place inside it.

Anyway, while that row exists on screen, you could use a reference/pointer to it and make visual changes to it. But once it goes far enough off screen, that “unit” is effectively destroyed (that’s a core part of the memory management aspect of TableView, to allow you to have hundreds of rows populate the view without keeping them all in memory at once). Basically, once the row is destroyed, any direct reference you stored for it will become invalid (because the Lua table ID reference died)… in your case, that means that “oldBuildingRow” will point to a non-existent object (and thus your attempts to change aspects of the row later will fail).

Now, let’s assume you scroll back to where you were (in the TableView). The row that was destroyed will come back into view, as expected, but it won’t be the same reference ID because it has been re-rendered as a new unit. That’s why your pointer for “oldBuildingRow” fails, because even though the row appears the same to you, it’s not the same ID internally from Corona’s standpoint.

You basically have the correct idea for keeping reference to the index of whatever row is “currently selected” or whatever. Where you tripped up slightly is by setting it as property of a row unit (“oldBuildingRow.index”) which, as I mention above, is not permanent and reliable because the row references are always changing as the rows appear/disappear.

Basic solution is this: store one reference to “oldBuildingRow” somewhere above/outisde the TableView code (to follow proper Lua scope rules). But do not set its value to a row’s reference (ID)… instead, set it to the direct and specific  index of the row. Then, in your “onRowRender” function, conditionally check if that row index matches the index value of “oldBuildingRow”… if it does, take whatever visual actions you want to make that row appear “selected” or whatever.

The other step is, you’ll need to re-render (:reloadData() API) the TableView in your “onBuildingRowTouch()” function, immediately after setting the “oldBuildingRow” variable to whatever row was just touched/selected. That API will basically loop through the on-screen visible rows and, because you added the conditional check in the previous paragraph, it will mark the proper row as selected while resetting the others.

Whew… that was a bunch of stuff to write. It’s probably more confusing than I intended it to be, so please just ask if something doesn’t make sense here.

Best regards.

Brent

Thanks for the lengthy explanation!  It sounds like the only reason my code is working as it stands may be that I haven’t been testing with enough data to have my table view rows destroyed as I drag the table around.  I will have more data soon, so I will be able to see if it works with more rows.  I’m guessing I will have to make the changes you recommended to make it work. 

You don’t happen to have any idea about that error message, do you?  It doesn’t look like most messages I’ve gotten (they usually give me a file and line number where things are going wrong, but that one doesn’t) and it happens even though the only instances of object:setFillColor in my code are protected by a

if object.setFillColor then object:setFillColor(color) end

structure (or even if the lines are commented out entirely).

That error looks familiar to me… where in the overall flow of things are you calling “reloadData()”?

See below for where I’m calling it…

Okay, now I have this:

local currentClassroomRowIndex local function onClassroomRowRender(event) local row = event.row local params = event.row.params local rowHeight = row.contentHeight local rowWidth = row.contentWidth print("Rendering row "..row.index) if row.index == currentClassroomRowIndex then print("ROW IS SELECTED") row:setRowColor({default = theme.selectedColor, over = theme.overColor}) else row:setRowColor({default = theme.backgroundColor, over = theme.overColor}) end local rowTitle = display.newText(row, params.name, 20, rowHeight \* 0.5, theme.font, 16) if rowTitle.setFillColor then rowTitle:setFillColor(theme.textColor) end rowTitle.anchorX = 0 end local function onClassroomRowTouch(event) local row = event.target local params = row.params if event.phase == "tap" or event.phase == "press" then currentClassroomRowIndex = row.index print("touched row "..row.index) classroomTableView:reloadData() end goButton.isVisible = true end

The print statements all print what you would expect. Result: the row just below the one I tap is highlighted with theme.selectedColor.  What the heck?

edit

Do you have the latest public release of Corona (3135) that was released a couple days ago? If so, I can point you to an updated sample which you could literally copy the code from to solve this (because I just updated it).

Brent

Okay, I just updated.  A sample would be great…I’m trying everything I can think of and it’s just not working for me.

edit:

I’m becoming strongly of the opinion that something is screwy with Corona here.  If my row render function looks like this:

local function onClassroomRowRender(event) local row = event.row local params = event.row.params local rowHeight = row.contentHeight local rowWidth = row.contentWidth local rowTitle = display.newText(row, params.name, 20, rowHeight \* 0.5, theme.font, 16) if rowTitle.setFillColor then rowTitle:setFillColor(theme.textColor) end rowTitle.anchorX = 0 if currentClassroomRowIndex and row.index == (currentClassroomRowIndex) then row:setRowColor({default = theme.selectedColor, over = theme.overColor}) local checkMark = display.newImageRect( row, "res/checkmark.png", 30, 30 ) checkMark.x = rowWidth - 30 checkMark.y = rowTitle.y else row:setRowColor({default = theme.backgroundColor, over = theme.overColor}) end end

the result is that the checkmark image is placed in the row I press but the row below that is highlighted.  I may give up on highlighting and just indicate the selected row with the checkmark.

OK, check out the “SampleCode” folder in your Corona application folder. Then go to “Graphics > FilterViewer”. See if that is the basic concept you’re trying to achieve, where (in this sample) you select a filter and the title of the row changes color… and then no matter if you scroll it way off the view or not, it remains that color.

This sample also utlizes what I feel is a more natural user experience, where the rows are highlighted on “tap” without actually using the “tap” listener for the TableView, but rather a combination of “press” and “release”.

If this sample would serve your model, I can help walk you through any of the code questions you have on it (there are, of course, big sections which you can outright ignore, but then replace with your own code to fit your app).

Brent

Okay, thanks - I didn’t realize all that sample code was there!  I ended up using the checkmark to indicate a selected row and selecting a row on release events since it works perfectly and doesn’t crash, but I may take a look at that code later just to see how it differs from what I had originally.