TableView Rows - Add an Image

You’re welcome.

I’m not sure when exactly the first on render row is called but i imagine it’s on insertRow so make sure your groups are set up before that.

As for positioning that all depends on your group set up. Check what you set for anchorX and anchorY and position accordingly. I believe the coordinates in the row view are center based so if you have anchorX = 0.5 (default) on your group, to center it, you should position it on x = row.contentWidth * 0.5

I may be wrong here but you will figure it out.

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