How do I safely resize objects from a resize event

Hi - can anyone help with this issue.

I have scene created (file ‘ending.lua’) which uses a local scene reference

local scene = composer.newScene()

and then I use the standard scene:create, scene:show events to create display objects. I keep a local reference to all objects created, and use

local sceneGroup = self.view

to get the top level group into which these objects are added. Within this I add a back button widget and position this at display.contentCenterX

I am using Runtime:addEventListener( “resize”, onResize ) to call my resize function to re-layout my scene.

When I try to reposition to the new display.contentCenterX I get the error message 'Attempt to index upvalue ‘backButton’ (a nil value)

So I’m not sure what’s happening - is the local file reference to backButton invalid even though it’s stored after scene:create?

Also, what is the safe way to get a reference to the sceneGroup these objects are contained within? Is backButton.parent enough?

Because the event is coming from Runtime, I’m wondering if there’s some thread sync issues I’m hitting.

Any help would be appreciated.

I’m guessing that you’ve defined backButton local inside of scene:create(). When you do so, only scene:create() can see the variable. This is “scope”. Simply define the variable near the top of your scene in the main chunk (local backButton). Then in scene:create() leave off the “local” (you declared it local at a higher scope level).

See: https://coronalabs.com/blog/2015/06/16/tutorial-scope-for-beginners/

Rob

Hi Rob

No, backButton is defined in the main chunk. I’ll copy in more of the code here for reference:

local composer = require( “composer” )

local scene = composer.newScene()

local backButton, buttonGroups

function scene:create( event )

    local sceneGroup = self.view

    buttonGroups = display.newGroup()

    –

  …

    sceneGroup:insert(background)

    backButton = widget.newButton({

        id = “backButton”,

        label = myData.text.startnew,

        width = 100,

        height = 32,

        onEvent = handleButtonEvent

    })

    backButton.x = display.contentCenterX

    backButton.y = display.contentHeight - 40

    sceneGroup:insert( backButton )

end

function scene:show( event )

    local sceneGroup = self.view

    imagepathp = “images/” … event.params.file … “p.png”

    imagepathl = “images/” … event.params.file … “l.png”

    if event.phase == “will” then 

        if (system.orientation == “portrait”) or (system.orientation == “portraitUpsideDown”) then

            endingImage = display.newImage ( imagepathp, display.contentCenterX, display.contentCenterY)

        else

            endingImage = display.newImage ( imagepathl, display.contentCenterX, display.contentCenterY)

        end            

        fitImage(endingImage, display.contentWidth - 20, display.contentHeight - 20, true)

        sceneGroup:insert(2, endingImage)

    end

end

local function resizeScreen( )

    local scene = composer.getScene(“ending”)

    local sceneGroup = endingImage.parent

    endingImage:removeSelf()

    endingImage = nil

    if (system.orientation == “portrait”) or (system.orientation == “portraitUpsideDown”) then

        endingImage = display.newImage ( imagepathp, display.contentCenterX, display.contentCenterY)

    else

        endingImage = display.newImage ( imagepathl, display.contentCenterX, display.contentCenterY)

    end            

    fitImage(endingImage, display.contentWidth - 20, display.contentHeight - 20, true)

    sceneGroup:insert(2, endingImage)

    backButton.x = display.contentCenterX  <---- This is the failing line

end

local function onResize( event )

    timer.performWithDelay( 50, resizeScreen, 1)

end

Runtime:addEventListener( “resize”, onResize )

I added the callback timer in case there was some thread safety issue, but still happens.

Ok - problem solved!

Schoolboy error - the problem was the addEventListener for scene create call was missing. Surprising that it worked as well as it did!

scene:addEventListener( “create”, scene )

Glad you solved it. But it appears to me this code is in the main chunk:

local function resizeScreen( ) &nbsp; &nbsp; local scene = composer.getScene("ending") &nbsp; &nbsp; local sceneGroup = endingImage.parent &nbsp; &nbsp; endingImage:removeSelf() &nbsp; &nbsp; endingImage = nil &nbsp; &nbsp; if (system.orientation == "portrait") or (system.orientation == "portraitUpsideDown") then &nbsp; &nbsp; &nbsp; &nbsp; endingImage = display.newImage ( imagepathp, display.contentCenterX, display.contentCenterY) &nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; endingImage = display.newImage ( imagepathl, display.contentCenterX, display.contentCenterY) &nbsp; &nbsp; end &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fitImage(endingImage, display.contentWidth - 20, display.contentHeight - 20, true) &nbsp; &nbsp; sceneGroup:insert(2, endingImage) &nbsp; &nbsp; backButton.x = display.contentCenterX &nbsp;\<---- This is the failing line end local function onResize( event ) &nbsp; &nbsp; timer.performWithDelay( 50, resizeScreen, 1) end Runtime:addEventListener( "resize", onResize )

If so, it will actually execute before scene:create() fires. You may still have a race condition on it still possibly running first. To be safe, I would add this line:

Runtime:addEventListener( "resize", onResize )

inside of the scene:show()'s will phase so that it can’t run until after you’re sure scene:create() has run. Also Lua is a one pass compiler. You’re functions have to be declared before you use them. I would recommend moving the rest of that code above scene:create() in your module.

Rob

Nice advice - thanks Rob

I’m guessing that you’ve defined backButton local inside of scene:create(). When you do so, only scene:create() can see the variable. This is “scope”. Simply define the variable near the top of your scene in the main chunk (local backButton). Then in scene:create() leave off the “local” (you declared it local at a higher scope level).

See: https://coronalabs.com/blog/2015/06/16/tutorial-scope-for-beginners/

Rob

Hi Rob

No, backButton is defined in the main chunk. I’ll copy in more of the code here for reference:

local composer = require( “composer” )

local scene = composer.newScene()

local backButton, buttonGroups

function scene:create( event )

    local sceneGroup = self.view

    buttonGroups = display.newGroup()

    –

  …

    sceneGroup:insert(background)

    backButton = widget.newButton({

        id = “backButton”,

        label = myData.text.startnew,

        width = 100,

        height = 32,

        onEvent = handleButtonEvent

    })

    backButton.x = display.contentCenterX

    backButton.y = display.contentHeight - 40

    sceneGroup:insert( backButton )

end

function scene:show( event )

    local sceneGroup = self.view

    imagepathp = “images/” … event.params.file … “p.png”

    imagepathl = “images/” … event.params.file … “l.png”

    if event.phase == “will” then 

        if (system.orientation == “portrait”) or (system.orientation == “portraitUpsideDown”) then

            endingImage = display.newImage ( imagepathp, display.contentCenterX, display.contentCenterY)

        else

            endingImage = display.newImage ( imagepathl, display.contentCenterX, display.contentCenterY)

        end            

        fitImage(endingImage, display.contentWidth - 20, display.contentHeight - 20, true)

        sceneGroup:insert(2, endingImage)

    end

end

local function resizeScreen( )

    local scene = composer.getScene(“ending”)

    local sceneGroup = endingImage.parent

    endingImage:removeSelf()

    endingImage = nil

    if (system.orientation == “portrait”) or (system.orientation == “portraitUpsideDown”) then

        endingImage = display.newImage ( imagepathp, display.contentCenterX, display.contentCenterY)

    else

        endingImage = display.newImage ( imagepathl, display.contentCenterX, display.contentCenterY)

    end            

    fitImage(endingImage, display.contentWidth - 20, display.contentHeight - 20, true)

    sceneGroup:insert(2, endingImage)

    backButton.x = display.contentCenterX  <---- This is the failing line

end

local function onResize( event )

    timer.performWithDelay( 50, resizeScreen, 1)

end

Runtime:addEventListener( “resize”, onResize )

I added the callback timer in case there was some thread safety issue, but still happens.

Ok - problem solved!

Schoolboy error - the problem was the addEventListener for scene create call was missing. Surprising that it worked as well as it did!

scene:addEventListener( “create”, scene )

Glad you solved it. But it appears to me this code is in the main chunk:

local function resizeScreen( ) &nbsp; &nbsp; local scene = composer.getScene("ending") &nbsp; &nbsp; local sceneGroup = endingImage.parent &nbsp; &nbsp; endingImage:removeSelf() &nbsp; &nbsp; endingImage = nil &nbsp; &nbsp; if (system.orientation == "portrait") or (system.orientation == "portraitUpsideDown") then &nbsp; &nbsp; &nbsp; &nbsp; endingImage = display.newImage ( imagepathp, display.contentCenterX, display.contentCenterY) &nbsp; &nbsp; else &nbsp; &nbsp; &nbsp; &nbsp; endingImage = display.newImage ( imagepathl, display.contentCenterX, display.contentCenterY) &nbsp; &nbsp; end &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fitImage(endingImage, display.contentWidth - 20, display.contentHeight - 20, true) &nbsp; &nbsp; sceneGroup:insert(2, endingImage) &nbsp; &nbsp; backButton.x = display.contentCenterX &nbsp;\<---- This is the failing line end local function onResize( event ) &nbsp; &nbsp; timer.performWithDelay( 50, resizeScreen, 1) end Runtime:addEventListener( "resize", onResize )

If so, it will actually execute before scene:create() fires. You may still have a race condition on it still possibly running first. To be safe, I would add this line:

Runtime:addEventListener( "resize", onResize )

inside of the scene:show()'s will phase so that it can’t run until after you’re sure scene:create() has run. Also Lua is a one pass compiler. You’re functions have to be declared before you use them. I would recommend moving the rest of that code above scene:create() in your module.

Rob

Nice advice - thanks Rob