TableView Rows - Add an Image

Thank you, @primoz.cerar – I got the imageGroups appearing exactly how I want them to appear inside the TableView.  So far, so good.

However, I have this isBounceEnabled set to true (by not including it) on the TableView, and when I scroll the rows too far up or too down (by pulling the rows too far up or too far down), imageGroups on the upper rows or on the lower row get removed, which results in runtime error:

2014-01-30 12:58:58.493 Corona Simulator[1259:507] Runtime error

my.lua:223: bad argument #-2 to ‘insert’ (Proxy expected, got nil)

stack traceback:

    [C]: in function ‘insert’

    my.lua:223: in function ‘_onRowRender’

    ?: in function ‘_createRow’

    ?: in function ‘_manageRowLifeCycle’

    ?: in function <?:521>

    ?: in function <?:218>

2014-01-30 12:58:58.493 Corona Simulator[1259:507] errorMessage = “my.lua:223: bad argument #-2 to ‘insert’ (Proxy expected, got nil)”

Why could this be?  I really don’t need any of the imageGroups removed from any of the rows.  In fact, I want them to stay where they are until this scene is removed from the screen.

Is there a way to stop this from happening?

Naomi

No not really. What happens is that when a row gets far enough off the screen it’s view gets removed and when it get’s back close to the screen it will get recreated and call your onRowRender function again for that row. You can find the row number in the onRowRender by checking event.row.index. For this reason it would be unwise to create whatever goes in the row prior to the onRowRender for that row as it will get removed when the row goes far enough off screen. Therefore create everything you need for the row in the onRowRender function. If you get bad performance from large images try using image sheets.

Naomi. what version of Corona SDK are you running?

Can you post your current code?

Just as a quick tutorial.  A tableView is on the outside a scrollView.  We insert into that scrollView “rows” each row itself is a display.newGroup that represents that individual row.  To keep memory down, we only “render” the row when it’s on screen or about to become on screen.

When you create the tableView it’s empty.  When you need to insert rows, you call insertRow.  Then when the tableView thinks a give row needs to show, it calls onRowRender() for that row.  Any row creation work (newText, newImageRect, etc) needs to happen inside onRowRender().  You’re given a reference to the row’s group and an ID number that represents the number of the row (so you can match it up to external data tables).   As you found out you don’t insert the rows in onRowRender, it’s just called when that row comes on screen.  The row has to already exist.

Thank you @primoz.cerar and @Rob.

I’m currently using daily build 2153.

When I create imageGroups inside onRowRender, it goes haywire – so I moved it outside the onRowRender function.

I put together a simple test project just for this, and the main.lua looks like this:

[lua]

display.setStatusBar( display.HiddenStatusBar )

local widget = require( “widget” )

local prod = {}

local img = {}

local txt = {}

local btn = {}

img.btnBuySheetOption = {

    width = 50, 

    height = 24,

    numFrames = 2,

    sheetContentWidth = 50,

    sheetContentHeight = 48

};

img.btnBuySheet = graphics.newImageSheet( “PNG/btnBuy50x24.png”, img.btnBuySheetOption )

img.prodSheetOption = {

    width = 40, 

    height = 40,

    numFrames = 25,

    sheetContentWidth = 200,

    sheetContentHeight = 200

};

img.prodSheetData = {

    start = 1,

    count = 25

};

img.prodSheet = graphics.newImageSheet( “PNG/prod1.png”, img.prodSheetOption )

local buyNow = function()

    print(“Buy this product now.”)

end

local createGroup = function()

    img.tag = {}

    img.prod = {}

    txt.units = {}

    txt.price = {}

    btn.buy = {}

    for i=1,5 do    

        prod[i] = display.newGroup()

        img.tag[i]= display.newImageRect( prod[i], “PNG/pricetag144x32.png”, 144, 32 )

        img.tag[i].x = 45

        img.prod[i] = display.newSprite( prod[i], img.prodSheet, img.prodSheetData )

        img.prod[i]:setFrame( math.random(1,25) );  img.prod[i].x = -45

        txt.units[i] = display.newText( prod[i], “x” … tostring(math.random(1,10000)), 0, 0, native.systemFontBold, 10 ); 

        txt.units[i].anchorX = 0

        txt.units[i].x = img.prod[i].x+25; txt.units[i].y = img.prod[i].y+5; txt.units[i]:setFillColor(0.5, 0.5, 0.5)

        txt.price[i] = display.newText( prod[i], “$” … tostring(math.random(100,900)), 0, 0, native.systemFontBold, 15 )

        txt.price[i].x = img.tag[i].x+40; txt.price[i]:setFillColor(0.5, 0.5, 0.5)

        btn.buy[i] = widget.newButton{

            sheet = img.btnBuySheet,

            defaultFrame = 1,

            overFrame = 2,

            width = 50,

            height = 24,

            onRelease = buyNow

        }

        btn.buy[i].x = img.tag[i].x+70;  btn.buy[i].y = img.tag[i].y-15;

        prod[i]:insert(btn.buy[i]);    

        prod[i].x = 86;    prod[i].y = 30;

        btn.buy[i].alpha = 0

    end

end    

    

local onRowRender = function( event )

    local row = event.row

    row:insert(prod[row.index])

end

local onRowTouch = function( event )

    local phase = event.phase

    local rowIndex = event.target.index

    if phase == “tap” then

        for i=1,5 do

            btn.buy[i].alpha = 0

        end

        btn.buy[rowIndex].alpha = 1

    end

end

local background = display.newRect(0,0,display.contentWidth, display.contentHeight)

background.x = display.contentCenterX; background.y = display.contentCenterY

createGroup()

local myTable = widget.newTableView

    {

        width = 230, 

        height = 230,

        hideBackground = true,

        noLines = true,

        hideScrollBar = true,

        onRowRender = onRowRender,

        onRowTouch = onRowTouch

    }

myTable.x = 120;  myTable.y = 185

    

for i=1,5 do

    myTable:insertRow{

        rowHeight = 50

    }

end

[/lua]

When I place createGroup() inside onRowRender function, it simply breaks.

If I keep it where it is now (which is right above the line where I create myTable), the table comes out just like I want it to – except it ends up with the runtime error I described above.

Thanks again for all your help.

Naomi

By the way, Rob, would you like me to send in this test project via bug submission page?

Naomi

I don’t think this is a bug.  If you’re going to call createGroup() inside onRowRender(), then createGroup() should just create one row’s worth of data.  In other words if you restructured createGroup() to take  a single parameter, which is the index and instead of looping 1, 5 just create the one for the index passed in, it would probably work how you want it to.  Does that make sense?

Ah, thank you, Rob, you are right.

All I have to do is to forward declare the variables:

[lua]

local widget = require( “widget” )

local prod = {}

local img = {}

local txt = {}

local btn = {}

img.tag = {}

img.prod = {}

txt.units = {}

txt.price = {}

btn.buy = {}

[/lua]

And then change the createGroup function to:

[lua]

local createGroup = function(n)

    prod[n] = display.newGroup()

    img.tag[n]= display.newImageRect( prod[n], “PNG/pricetag144x32.png”, 144, 32 )

    img.tag[n].x = 45

    img.prod[n] = display.newSprite( prod[n], img.prodSheet, img.prodSheetData )

    img.prod[n]:setFrame( math.random(1,25) );  img.prod[n].x = -45

    txt.units[n] = display.newText( prod[n], “x” … tostring(math.random(1,10000)), 0, 0, native.systemFontBold, 10 ); 

    txt.units[n].anchorX = 0

    txt.units[n].x = img.prod[n].x+25; txt.units[n].y = img.prod[n].y+5; txt.units[n]:setFillColor(0.5, 0.5, 0.5)

    txt.price[n] = display.newText( prod[n], “$” … tostring(math.random(100,900)), 0, 0, native.systemFontBold, 15 )

    txt.price[n].x = img.tag[n].x+40; txt.price[n]:setFillColor(0.5, 0.5, 0.5)

    btn.buy[n] = widget.newButton{

        sheet = img.btnBuySheet,

           defaultFrame = 1,

        overFrame = 2,

        width = 50,

        height = 24,

        onRelease = buyNow

    }

    btn.buy[n].x = img.tag[n].x+70;  btn.buy[n].y = img.tag[n].y-15;

    prod[n]:insert(btn.buy[n]);    

    prod[n].x = 86;    prod[n].y = 30;

    btn.buy[n].alpha = 0

end    

[/lua]

And then change the onRowRender function to:

[lua]

local onRowRender = function( event )

    local row = event.row

    createGroup(row.index)

    row:insert(prod[row.index])

end

[/lua]

It took me a while to understand how this thing is being put together – but I think I got it now.  I appreciate you pointing it out to me.  Thanks again.  

Naomi

I think you got it!

You could loose the forward declarations and just return the displayGroup you created in the createGroup function unless you need to reference those objects somewhere else but I don’t think you should as you have found out that they can be removed by the tableView.

Just make all the variables local in createGroup like:

local prod = display.newGroup()  --you don’t need a table as you are only creating one row at a time.

.

.

.

return prod

In the onRowRender:

local onRowRender = function( event ) local row = event.row row:insert(createGroup(row.index)) end

Ah, yes, @primoz.cerar, even cleaner.  Thank you.  

I change some value/state of objects inside each of the display group created in createGroup, but I don’t think I’ll change any property of the display group itself once it is created – so you’re right, I suppose I don’t need to declare prod as table up top – but I believe I still need to declare objects that are placed inside the group declared outside the createGroup function because I change some of their values dynamically – and not upon onRowRender.  (For example, the number of units will either decrease or increase, the alpha value of btn.buy will change, etc.)

Thanks again!

Naomi

Ok then yes you need those referenced HOWEVER as you saw, the tableView will remove your objects when the row goes out of view so you will have a problem knowing whether your button for example still exists. If you try to change anything on it and it has been removed you will get an error. It’s undocumented but you can check if a rows view currently exists or not like this:

if tableView._view._rows[index]._view then

  --rows view exists so your button should too

else

  --rows view doesn’t exist so you don’t have to change anything, but make sure you set the values you want in the onRowRender if the row comes back in to view.

end

Thank you, @primoz.cerar.  That’s really helpful.  I’ll keep the code snippet.

Thanks again!

Naomi

I’m having trouble figuring out how to go about inserting display groups to TableView rows. 

I’ve read the thread above carefully, but I’m still not getting it.

I also looked at Corona documentation:

http://docs.coronalabs.com/api/library/widget/newTableView.html

http://docs.coronalabs.com/api/type/TableViewWidget/insertRow.html

Sample code that comes bundled with Corona SDK does not offer any insight as to what I need to do either.

So, as autolib noted up top, I feel Corona hasn’t really offered any documentation or sample code anywhere  that could help us – or perhaps they are so very hidden that I’ve failed to find them, or I just don’t know what to look for – so here I am, still no solution in sight.

Here’s what I’ve tried.  Clearly, I’m missing something, but I don’t know what that is:

[lua]

– localGroup is self.view in composer

– myTable is a local variable forward referenced up top

local imageGroup = {}

local onRowRender = function()

    --[[

    Here I created display groups:

        imageGroup[1]

        imageGroup[2]

        imageGroup[3]

        imageGroup[4]

        imageGroup[5]

        Each of these display groups has 4 pieces of display objects inside

    --]]

    local row = event.row

    local rowH = row.contentHeight

    local rowW = row.contentWidth

    for i=1,5 do

        myTable:insertRow{

            rowHeight = 55

        }

        row:insert( imageGroup[i] )

    end

end

local onRowTouch = function()

    – do whatever needs to be done onRowTouch

end

myTable = widget.newTableView

{

    width = 230, 

    height = 240,

    backgroundColor = { 0.9, 0.9, 0.9 },

    hideBackground = false,

    onRowRender = onRowRender,

    onRowTouch = onRowTouch,

}

localGroup:insert( myTable )

myTable.x = display.contentCenterX-120;  myTable.y = display.contentCenterY+20

[/lua]

Why don’t I see the display groups neatly appearing in row inside the TableView object?  What am I missing?  Could it be possible that TableView cannot hold display groups – could it be the reason why my attempt would never work?

I’d so appreciate any help anyone can offer.

Naomi

please post your onRowRender function. Then we can se what is going on.

I don’t know what happened to the code…  formatting got so messed up (although, in editing mode, it looks fine.)

So here’s the copy of the same code.  Hopefully, it won’t get messed up:

[lua]

– localGroup is self.view in composer

– myTable is a local variable forward referenced up top

local imageGroup = {}

local onRowRender = function()

    --[[

    Here I created display groups:

        imageGroup[1]

        imageGroup[2]

        imageGroup[3]

        imageGroup[4]

        imageGroup[5]

        Each of these display groups has 4 pieces of display objects inside

    --]]

    local row = event.row

    local rowH = row.contentHeight

    local rowW = row.contentWidth

    for i=1,5 do

        myTable:insertRow{

            rowHeight = 55

        }

        row:insert( imageGroup[i] )

    end

end

local onRowTouch = function()

    – do whatever needs to be done onRowTouch

end

myTable = widget.newTableView

{

    width = 230, 

    height = 240,

    backgroundColor = { 0.9, 0.9, 0.9 },

    hideBackground = false,

    onRowRender = onRowRender,

    onRowTouch = onRowTouch,

}

localGroup:insert( myTable )

myTable.x = display.contentCenterX-120;  myTable.y = display.contentCenterY+20

[/lua]

Ah yes. You are inserting rows in your onRowRender function. You are only supposed to add objects to the rows view which is event.row in the onRowRender function.

You should insert the rows outside of that function after you ave created the tableView.

Also note that onRowRender is called once for each row so only insert elements for one row at a time.

Thank you, @primoz.cerar – but I’m afraid I fail to understand what you mean?

What does it mean to “add objects to the rows view, which is event.row in the onRowRender function” – and what does it also mean to “insert the rows outside of that function”…?  Sorry for not getting it, but I still don’t understand.

Hmmm… add display groups to the rows view – which is event.row in the onRowRender function – but then I need to insert the rows outside of onRowRender function.  I’m confused.

Naomi

 -- localGroup is self.view in composer -- myTable is a local variable forward referenced up top local imageGroup = {} local onRowRender = function() --[[Here I created display groups: imageGroup[1] imageGroup[2] imageGroup[3] imageGroup[4] imageGroup[5] Each of these display groups has 4 pieces of display objects inside --]] local row = event.row local rowH = row.contentHeight local rowW = row.contentWidth -- just add the elements of the current row being rendered. Mind that indexes are not continuous if you delete rows row:insert( imageGroup[row.index] ) end local onRowTouch = function() -- do whatever needs to be done onRowTouch end myTable = widget.newTableView { width = 230, height = 240, backgroundColor = { 0.9, 0.9, 0.9 }, hideBackground = false, onRowRender = onRowRender, onRowTouch = onRowTouch, } localGroup:insert( myTable ) myTable.x = display.contentCenterX-120; img.table.y = display.contentCenterY+20 --insert rows here for i=1,5 do myTable:insertRow{ rowHeight = 55 } end

Hope this helps

Ah, thank you so much, @primoz.cerar!!  Super appreciated.

Naomi

It looks like I need to create display groups (imageGroup[1], imageGroup[2], etc) before I create the table inside the scene:create function of the composer, but they are now at least appearing inside the table.  Next I have to figure out how to properly position them inside the row (right now, half of it gets cut off to the left) – but at least I’m half way there.

Thanks again, @primoz.cerar!

Cheers,

Naomi