Posted on by

FAQ Graphics

It’s Wednesday and time for another five frequently asked questions (FAQ).

Question 1

Why do images take longer to load on the new iPad (retina)?

Answer

This issue came up when a user was using large JPEG images on the new iPad Retina and noticed the time to load the images were a lot higher than on the iPad2. One of the reasons is the new iPad accepts larger image sizes, but we also found out a few things that might help if you have images that take a long time to load.

First off, use non-progressive JPEGs. Progressive JPEG take longer to load. How you compress JPEGs makes a difference in load times. Compressing to 70% or 80% can cut down the time to load. You should experiment with the compression value to balance image quality and load times.

The other tip is to use PNGs instead of JPEGs if you can. Apple runs PNGs through a utility, pngcrush, that optimizes the images to reduce the size and load times on iOS devices. Please note that the images will not be viewable on the Mac after they are run through this program.

Here is a link to an article that talks more about the iPad Retina and image compression.

Question 2

Why can’t I display objects on top of WebPopups or other native display objects?

Answer

WebPopups (like navite.newTextField, native.newTextBox, and native.showAlert. etc.) are native display objects and not part of openGL that Corona uses to display objects on the screen. Native display objects are always displayed on top of Corona Display objects, which is something we don’t have any control over. The only solution to this problem is to implement the equivalent native object using Corona’s display objects.

Question 3

How do I pass a parameter to a function using timer.performWithDelay?

Answer

Functions like timer.performWithDelay, transition.to and transition.from require a reference to a function, which means you can’t call the function directly.

This is the typical way a function is called in timer.performWithDelay.


local rect = display.newRect( 0, 0, 100, 100 )

local function myRemove()
rect:removeSelf() -- remove the rect
end

timer.performWithDelay( 1000, myRemove )

The above will create a rect and remove it 1000 milliseconds (1 second) later.

Lets say you want to have a generic remove function where you pass in the object to be removed. You could try something like this.


local rect = display.newRect( 0, 0, 100, 100 )

local function myRemove( object )
object:removeSelf() -- remove the rect
end

timer.performWithDelay( 1000, myRemove( rect ) )

You would expect the above to remove the rect, and it does. The only problem is it removes it before the timer even starts. The result is the object is created and removed without a delay. The reason is myRemove function is executed when the timer is initialized and the returned value is used as the function reference. In this case it will be nil, since we didn’t return anything from the function. So after the timer fires, no operation is performed.

So how can we pass in our object to the remove function? The answer is “closures”. Closures create a local variable that saves the parameter and returns a unique function reference that will access the saved parameter when called. This way you can have one function (myRemove in our example), used for multiple objects. Here is the code modified to use closures.


local rect = display.newRect( 0, 0, 100, 100 )

local function myRemove( object )
return function()
object:removeSelf() -- remove the rect
end
end

timer.performWithDelay( 1000, myRemove( rect ) )

You will notice that myRemove function now returns an anonymous function that uses object as a parameter. The object passed to myRemove is store as a local variable and used the next time that instance of the anonymous function is called. A new anonymous function will be created each time myRemove function is called.

The code to remove an object is pretty simple and could be embedded in the timer.performWithDelay call, but the real power is when you need to do a lot more than remove the object. Closures are also useful for other situations besides the example shown here.

Question 4

What is the best way to remove “print” messages from release code?

Answer

Print statements are good to have in your code during development and debug, but they shouldn’t be there in release code (e.g., code you submit to the App Store). The print messages consume CPU cycles and are viewable by anyone who runs your program while connected to Xcode (iOS) or ADB logcat (Android). You could search your code and comment out all the print statements but there’s an easier way.


function print() end -- redefine print to do nothing

print( "This should not print" )

This overrides the Lua print API with a null function. The result is no print messages.

If you have messages that you need print no matter what, you could define a new function that calls the old print API.


printAlways = print -- save the existing print function
function print() end -- redefine print to do nothing

print( "This should not print" )
printAlways( "This will always print" )

We can refine the above code to use a flag to control the printing.


local releaseBuild = true -- set to true/false to print/suppress messages
printAlways = print -- save the existing print function

if releaseBuild then
function print() end
end

print( "This will print if releaseBuild is false" )
printAlways( "This will always print" )

The above methods won’t remove the messages from your app, only suppress the output at runtime. If you have lots of print statements in your code and worried about the size of your app, your only solution is to remove or comment out the print messages.

Question 5

What does “assert” and “pcall” do?

Answer

Both calls are useful during development of your app and should be part of your Corona toolbox. Assert conditionally aborts and prints an error message if something something you expect to evaluate as true, is not. One use is to abort if a bad parameter is passed to a function (e.g., expecting a table but received a string instead).

The following code will catch a missing image file and sends an error message to the terminal/console.


local img = display.newImage("imageWithWrongName.png")
assert( img, "Error: Image not found!" )

The assert call aborts the program and prints the error message if the first parameter is not true. In the above case, a missing image file will set img to nil, and activate the asset. You generally don’t want asserts active in your code for a release build, so you can use the conditional code mentioned in the question 4 to null out the assert function for release.

The pcall call can be used to catch errors in your program and handle them without aborting the program. It does a “protected call” because it traps errors and returns a true/false status to indicate if the call failed. This can be useful when you want to know if something fails and handle the situation.

Here is an example of how you use pcall.


function myPrint( value )
    local foo = value .. nil
end
 
print( pcall( myPrint, "hello" ) ) -- print false and error message
print( "The program continues to run after the above error" )

The above function will fail and normally stop the simulator because we are trying to concatenate a nil value. The pcall will catch and display the error without stopping the simulator.

That’s it for this edition of FAQ Wednesday. I hope you enjoyed it and learned something new.


Posted by . Thanks for reading...

9 Responses to “FAQ Wednesday”

  1. Robert

    On Question #3 Your saying to use the following statment:

    timer.performWithDelay( 1000, myRemove( rect ) )

    however i thought this didn’t work. to properly pass the parameter “rect” to the myRemove function i thought you had to define it this way:

    timer.performWithDelay( 1000, function() myRemove( rect ) end )

    Reply
  2. Mo

    Thanks so much Tom! First because I always wondered about all those prints for release version. Second about timers and passing parameters. Both of those questions were resolved today!

    Please, please them coming!

    Mo
    (LairdGames)

    Reply
  3. Michael

    Thanks!

    Please write about in-app purchase with downloadable content. This is one of the topic where are too many questions and almost no answers.

    Reply
  4. Nevin Flanagan

    In the case of the local function for the remove function above, there’s no real value to assigning the argument to a new local name. Argument names are valid local names and can be used as upvalues just as easily as explicit locals can.

    i.e.

    local function myRemove( object )
    return function() — timer event arg will be ignored
    object:removeSelf() — remove the rect
    end
    end

    Reply
  5. Tom Newman

    @Robert, The way you mentioned works fine if all you need to do is remove the object. My final example works because it uses closures to act on the object that was passed in the function call when the timer was initialized. After the timer fires, it uses the value returned from calling “myRemove” as the onComplete listener.

    @Nevin, you are right about not needing the “local” assignment of object. My final version of that code sample removed the local variable but it didn’t make it into my final posting. I updated the code example. Thanks.

    Reply
  6. Ran

    I LOVED seeing that you tackled the issue of slow loading 2048 x 1536 images for the new iPad. I am definitely having an issue with the length of time it is taking to load these images. I was excited to read about any possible solution…

    I found a website that talked about how to turn .jpg’s into progressive .jpg’s. I’m assuming that .jpgs are non-progressive by default..? and since you say that non-progressive images load faster, I don’t believe I need to do anything more on this end…

    Does compressing a .jpg refer to the “quality” at which it is saved? I’ve already lowered the quality in the “Save dialog” of photoshop as far as I can to lower file sizes and load times are still slow. If I lower it any further, the quality of the images start to suffer.

    As for .png’s loading faster than .jpg’s… when I save my .jpg images as .png files, the file sizes are 3 times as large. I don’t believe the iPad will load a 1.5MB .png faster than it would a 500k .jpg, would it?

    For now, I am still at a loss about how to display 2048 x 1536 full screen retina images on the new iPad with the most efficient load time for the images. =(

    Reply
  7. Naomi

    Great FAQ. Thank you for posting this. I especially appreciated the FAQ on timer + Closure, which made it much easier for me to understand and start using. I wish I read this FAQ the moment it was posted. Yesterday, I spent sometime adding a flag to each print statement, switching between release build and dev build (which made each print statement to be triggered only when the flag is set to dev build.) Using your example code, I could’ve just added four lines of code in main.lua and be done with the task.

    And if I may, I’d love to see FAQ on Facebook API. With Facebook API, if it can be implemented at all with Corona SDK, I’d love to see how we may integrate FB Friends request, like requesting FB friends to play/use the game/app, and (assuming FB will return success/failure of the request) the app/game would take action based on its success — such as give something special to the user for successful friends request while withholding such boon when friends request fail. I’d also love to see how we may integrate FB Score API, like automatically posting something like “player A has scored 1000 points, passing the high score of player B” to both player A, player B and other friends of player A who are playing the game.

    I’d also like to learn more about IAP as @Michael requested above. Specifically more about transaction.state for transactionCallback(event). If you can post a laundry list of all possible transaction.state (and include which applies to iOS and/or Android) and what kind of pre-canned text strings that the store would return, it would help greatly. I recently had an unfamiliar alert message show up and was not prepared for it. If I know specific transaction.state that triggers specific App Store message that gets displayed to the user, it would help quite a bit.

    Hmmmm… maybe what I’m asking for is beyond FAQ…? If so, I’d love to see the Tutorial on these (as well as document/reference page updates.)

    Reply

Leave a Reply

  • (Will Not Be Published)