TableView- row objects disappearing permanently when scrolled out of view

Hey guys,

Really weird bug.  I’ve got a table view widget going.  I’m adding image and text objects to each row.  But when I scroll down and scroll back up, the rows that were pushed up and off the screen will be missing some of their display objects.  I’ve narrowed it down to a particular function, if the image is declared outside of the function, it’ll reappear when the rows snap back.  But if it’s in the function, it disappears when the rows snap back.

I need the function to determine if the image should be added or not (there’s a db of images and it’s listing each image from the db, so it’s checking if there’s an image to be added - line 36, “if imageCounter <= (numberOfImages ) then…”). Unfortunately this seems to be the widget breaking function.

If I just add stuff in without regard to the database, it works fine.  I can pull images from the database and list them out fine in the tableView, but once I do they disappear permanently when they are scrolled off screen.

It’s driving me crazy!  Any help would be appreciated.  I think it’s similar to this guys issue, but I couldn’t get his fix to work for me, and there were no other suggestions: https://forums.coronalabs.com/topic/58464-tableview-row-content-disappearing-when-scrolling-intermittent/

Thanks in advance for any help!!!

Here’s the code sliced out of my project (so it might not run as-is)

----------------BEGIN TABLE VIEW WIDGET--------------- local widget = require( "widget" ) -- local rowNumber = 0; --this is what is created in a row function onRowRender( event ) -- Get reference to the row group local row = event.row --get row number rowNumber = row.index; -- local rowHeight = row.contentHeight local rowWidth = row.contentWidth -- local image1 = display.newImageRect(row, "images/shelf.png",968,138); image1:scale( .50, .5 ) image1.anchorX = 0; image1.anchorY = 0; image1.x = 0+offset; image1.y = 150; -- local rowText = display.newText(row,"Row #"..rowNumber, 0, 0, \_font, 24); rowText:setTextColor(0) rowText.anchorX = 0; rowText.anchorY = 0; rowText.x =0+offset; rowText.y =150; -- local numberOfImages = 20 numberOfRowsForAllImages = 7 imageDisplayTable = {} -- if rowNumber \<= numberOfRowsForAllImages then --placing 3 images on the shelf for j = 1,3,1 do if imageCounter \<= (numberOfImages ) then --retrieve row data local rowDataTable = retrieveRow(imageCounter) -- imageDisplayTable[imageCounter] = display.newImageRect(row, "images/"..rowDataTable[4]..".png",138,200); imageDisplayTable[imageCounter].id = imageCounter imageDisplayTable[imageCounter]:scale( .7, .7 ) imageDisplayTable[imageCounter].x = 80+(150\*j); imageDisplayTable[imageCounter].y = 110; imageDisplayTable[imageCounter]:addEventListener( "touch", onObjectTouch ) -- imageCounter = imageCounter + 1; end end end -- end numberOfShelves = 6; -- function createTableView() -- Create the widget tableView = widget.newTableView { left = -84, top = 160, height = 680, width = 768, bottomPadding = 240, onRowRender = onRowRender, onRowTouch = onRowTouch, listener = scrollListener, backgroundColor = { default = { 0,0,0, 0 }}, noLines = true, hideBackground = true, } function insertAllTableViewRows() print("in insertAllTableViewRows function") -- for i = 1, numberOfShelves do -- tableView:insertRow{ rowHeight = 200, rowColor = { default = { 255,0,0, 0.0 }}, } end -- end insertAllTableViewRows() -- pageGroup:insert(tableView) end createTableView()

bump - anyone else encounter this problem?

Rob Miracle, are you around- do you know what the issue could be?

thanks!!!

I’m no Rob, however one thing I notice that could cause strange issues is on line 31. You have a table variable that is not local to the function. Every time onRowRender is called you are overwriting the variable instead of creating a new local empty table. This would cause a memory leak and odd behavior with the objects being replaced.

After a quick look that’s all that stood out to me.

I think @Anderoth is on to something. You have several global variables being used and one that’s local but outside of the onRowRender() function.

local rowNumber = 0; --this is what is created in a row function onRowRender( event ) -- Get reference to the row group local row = event.row --get row number rowNumber = row.index;

I think this “rowNumber” variable will be a problem. You can’t guarantee that rowNumber will be the value you expect it to be. Rows are not rendered in a particular order.  I would make  it local to onRowRender() as a cached value of that row’s .index. Keep in mind that off screen rows won’t be rendered until they need to be moved on screen. The way you’re looping over the book list appears as if you’re assuming the rows are going to be processed in order once but that’s not the case.

Personally I don’t like onRowRender to have to make any assumptions about what data it should be fetching from your dataset. While I don’t always think in a MVC (Model-View-Controller) fashion, this is one time where MVC makes more sense.

Your database is your database. The code managing it shouldn’t care at all about how it’s displayed. The View is responsible for showing the data, but it shouldn’t have to care about your data’s organization. The Controller’s job is to be the middle man between the two.

In this case think of onRowRender() as having one job. Draw an arbitrary row. It can’t make any assumptions about which row it should be drawing. The Controller in this case is the insertRows() function and the loop around it. It’s job is to iterate over the data and provide onRowRender with what it needs.

There is a feature we added to the tableView code several years ago that lets you pass parameters to onRowRender. It’s works something like this:

 for i = 1, #myData.settings.locations do locationsTableView:insertRow({ isCategory = isCategory, rowHeight = rowHeight, rowColor = rowColor, lineColor = lineColor, params = { name = myData.settings.locations[i].name, latitude = myData.settings.locations[i].latitude, longitude = myData.settings.locations[i].longitude, postalCode = myData.settings.locations[i].postalCode, selected = myData.settings.locations[i].selected, noDelete = myData.settings.locations[i].noDelete, id = i }, }) end

Now my insertRow() function is responsible for telling onRowRender what to do, not onRowRender having to know what row its in. Inside onRowRender(), you get the data like:
 

local function onLocationRowRender( event ) -- Get reference to the row group local row = event.row local params = event.row.params -- Cache the row "contentWidth" and "contentHeight" because the row bounds can change as children objects are added local rowHeight = row.contentHeight local rowWidth = row.contentWidth -- Each row will have three potentially visible components: -- The location text (rowTitle), the delete button (off screen), and a check mark if selected. -- Render each part local rowTitle = display.newText( row, params.name, 20, rowHeight \* 0.5, myData.font, 16 ) . . .

My onRowRender() doesn’t know anything about my database or what record it’s drawing. In your case, you need each row to know about three specific books. You could do something like:

 local books = {} for i = 1, numberOfShelves do -- local books[1] = (i - 1) \* 3 + 1 local books[2] = books[1] + 1 local books[3] = books[2] + 1 tableView:insertRow{ rowHeight = 200, rowColor = { default = { 255,0,0, 0.0 }}, params = { books = books } } end

Now all onRowRender has to do is loop over the params.books table and render those three books.

Rob

bump - anyone else encounter this problem?

Rob Miracle, are you around- do you know what the issue could be?

thanks!!!

I’m no Rob, however one thing I notice that could cause strange issues is on line 31. You have a table variable that is not local to the function. Every time onRowRender is called you are overwriting the variable instead of creating a new local empty table. This would cause a memory leak and odd behavior with the objects being replaced.

After a quick look that’s all that stood out to me.

I think @Anderoth is on to something. You have several global variables being used and one that’s local but outside of the onRowRender() function.

local rowNumber = 0; --this is what is created in a row function onRowRender( event ) -- Get reference to the row group local row = event.row --get row number rowNumber = row.index;

I think this “rowNumber” variable will be a problem. You can’t guarantee that rowNumber will be the value you expect it to be. Rows are not rendered in a particular order.  I would make  it local to onRowRender() as a cached value of that row’s .index. Keep in mind that off screen rows won’t be rendered until they need to be moved on screen. The way you’re looping over the book list appears as if you’re assuming the rows are going to be processed in order once but that’s not the case.

Personally I don’t like onRowRender to have to make any assumptions about what data it should be fetching from your dataset. While I don’t always think in a MVC (Model-View-Controller) fashion, this is one time where MVC makes more sense.

Your database is your database. The code managing it shouldn’t care at all about how it’s displayed. The View is responsible for showing the data, but it shouldn’t have to care about your data’s organization. The Controller’s job is to be the middle man between the two.

In this case think of onRowRender() as having one job. Draw an arbitrary row. It can’t make any assumptions about which row it should be drawing. The Controller in this case is the insertRows() function and the loop around it. It’s job is to iterate over the data and provide onRowRender with what it needs.

There is a feature we added to the tableView code several years ago that lets you pass parameters to onRowRender. It’s works something like this:

 for i = 1, #myData.settings.locations do locationsTableView:insertRow({ isCategory = isCategory, rowHeight = rowHeight, rowColor = rowColor, lineColor = lineColor, params = { name = myData.settings.locations[i].name, latitude = myData.settings.locations[i].latitude, longitude = myData.settings.locations[i].longitude, postalCode = myData.settings.locations[i].postalCode, selected = myData.settings.locations[i].selected, noDelete = myData.settings.locations[i].noDelete, id = i }, }) end

Now my insertRow() function is responsible for telling onRowRender what to do, not onRowRender having to know what row its in. Inside onRowRender(), you get the data like:
 

local function onLocationRowRender( event ) -- Get reference to the row group local row = event.row local params = event.row.params -- Cache the row "contentWidth" and "contentHeight" because the row bounds can change as children objects are added local rowHeight = row.contentHeight local rowWidth = row.contentWidth -- Each row will have three potentially visible components: -- The location text (rowTitle), the delete button (off screen), and a check mark if selected. -- Render each part local rowTitle = display.newText( row, params.name, 20, rowHeight \* 0.5, myData.font, 16 ) . . .

My onRowRender() doesn’t know anything about my database or what record it’s drawing. In your case, you need each row to know about three specific books. You could do something like:

 local books = {} for i = 1, numberOfShelves do -- local books[1] = (i - 1) \* 3 + 1 local books[2] = books[1] + 1 local books[3] = books[2] + 1 tableView:insertRow{ rowHeight = 200, rowColor = { default = { 255,0,0, 0.0 }}, params = { books = books } } end

Now all onRowRender has to do is loop over the params.books table and render those three books.

Rob