Awhile ago, I was approached to do a custom app for someone. The app itself is finished (not published yet) and in the process of creating it I thought it would be a good idea to publish the source for the dynamic image slider control I created. Well, not content with just publishing a simple control, I decided to create an entire demonstration app to see it in action. The Flickr Photo Browser is the result of this. In this app you'll see how to download data and images as well as how to create your own custom controls.

Using the app itself is simple. When it first starts up, the user is presented with a grid view of different categories with their associated icons. By tapping one of the categories, the app will perform a Flickr image search for images related to that category. It will download the RSS feed and related images from Flickr and then present them in an image slider control. The user can then swipe forward or backward to view the different images. When the user taps on an image, the title of the photo will be displayed.
So let's get to some code. Before I do, it should be noted that I'm not going to go through every source file and every line. I would risk losing your attention and my sanity. What I will do however is explain each of the major portions of the app and how it works.
There are a multitude of ways to get something done in Lua. I myself use different patterns interchangeably. To keep things simple, I use the same pattern for each of the files in this project (this does not include the external libraries that I've downloaded such as rss.lua and xml.lua). Before we dig into the actual app code, let's detour for a second and I'll show you how I setup a new module before I start writing the code for it.
display.newGroup is my friend. I use it quite often and it serves as the base for almost every new module that I create (assuming that module displays a UI of some sort). Due to the expando nature of Lua, I'm able to easily add new properties to a group in order to effectively know what state that object is in. When I create a new module, I always create a new() method that returns a display.newGroup object. An example of this might be the new() method for a "Hello World" module.
1 2 3 4 5 6 7 | module(..., package.seeall) function new() local helloWorldGroup = display.newGroup() helloWorldGroup.name = "Hello World" return helloWorldGroup end |
To instantiate this object, I simply require the correct file and then call the new method like so (assuming my module is in the file named "helloWorld.lua"):
1 2 | local helloWorld = require("helloWorld") local myHelloWorld = helloWorld.new() |
Of course the module would do nothing if that's all it did so I add new methods to it. For each method that I create, I pass in the display.newGroup object that it returned in it's new method. Let's add a new method named sayHello:
1 2 3 4 5 6 7 8 9 10 11 | module(..., package.seeall) function new() local helloWorldGroup = display.newGroup() helloWorldGroup.name = "Hello World" return helloWorldGroup end function sayHello(helloWorldGroup) print(helloWorldGroup.name) end |
To call it, simply call sayHello and pass in the correct object:
1 2 3 | local helloWorld = require("helloWorld") local myHelloWorld = helloWorld.new() helloWorld.sayHello(myHelloWorld) |
As I mentioned earlier, that's just one way of designing modules in Lua and it's the one I chose for this demo/tutorial to keep things simple. I could have just as easily used the ':' operator instead freeing me from having to pass in the object to each method.
What I described above is great for a module that needs to display something on the screen, hence the reason for creating a new display group. The question however remains, what if you want to create a module using this pattern that doesn't need to display a UI? The answer is quite easy, just return a table instead of a display group. Our module code then turns into something like (notice the use of {}):
1 2 3 4 5 6 7 | module(..., package.seeall) function new() local helloWorldObject = {} helloWorldObject.name = "Hello World" return helloWorldObject end |
Instantiating and calling methods on that returned object follow the same pattern as before. However, the only thing you wouldn't want to do is call object:insert to insert that object into a display group. Now, with that quick primer on basic LUA/Corona module patterns out of the way, let's jump into the Flickr Photo Browser code. Let's start by examining the RSS Downloader object, followed by a dive into the custom controls and wrap it up by seeing how all the pieces fit together.
The RSS Downloader module is responsible for downloading an RSS feed and any of its related images. While I use this to download feeds from Flickr, you can just as easily use this to download feeds from any RSS source. The source code for this module can be found in the file rssDownloader.lua.
So first things first, we need a way to instantiate our RSS Downloader object. In the same way I demonstrated above, we simply create a new object and return it.
1 2 3 4 5 6 7 8 | module(..., package.seeall) local rss = require("rss") function new() local rssObject = {} return rssObject end |
Once the object is instantiated, we can use it to download an RSS feed. The RSS Downloader will make a network request and download the requested feed and place it in the app's Documents directory. However, we also need a way to callback the caller to relay status messages and signify an error if one were to occur. So our downloadFeed method will take as parameters the object we're operating on, the url to the feed we want to download and callbacks for status and error messages. We simply save those parameters on our original rssObject object we created earlier when the caller called new().
1 2 3 4 5 6 7 8 | function downloadFeed(rssObject, url, statusCallback, errorCallback) cleanDocumentsDirectory() rssObject.statusCallback = statusCallback rssObject.errorCallback = errorCallback network.download( url, "GET", function(event) onNetworkStatus(event, rssObject) end, "rss.xml", system.DocumentsDirectory ) end |
I should point out a few things in the method above. The first thing we do is clear out the contents of our app's Documents directory. Next, we save the callback objects by adding new properties to our rssObject. Finally, we call network.download to download the RSS feed to a file named "rss.xml" in our app's Documents directory. We will be notified via callback whether the download succeeded or failed since we specify a callback method to use as the 3rd parameter to network.download. If we receive an error, we can simply call the rssObject.errorCallback method that we saved earlier, otherwise we can go ahead and parse the feed and extract any images out. The onNetworkStatus method is as follows:
1 2 3 4 5 6 7 | function onNetworkStatus(event, rssObject) if ( event.isError ) then rssObject.errorCallback() else parseFeed(rssObject) end end |
The method itself is pretty simple. We check to see if an error occured and if so, call the error callback. If the call succeeded, then we can go ahead and parse the feed to download any images we find in the feed. So that leads us to the parseFeed method. parseFeed uses the RSS library found on Code Exchange to initially parse the RSS feed into a Lua data structure. It then loops through all the "stories", looks at the url for the associated image for that story and downloads it to the local file system. While it's doing this, it is sending status events to the initial consumer of the RSS Downloader module using the rssObject.statusCallback method that was setup earlier. You'll see later that we use this status callback mechanism to hook up a progress bar to our application to inform the user that something is actually happening. The parseFeed, parseStory and onImageDownloaded methods all work together to download the images based on information in the feed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | function parseFeed(rssObject) rssObject.stories = {} rssObject.stories = rss.feed("rss2.xml", system.DocumentsDirectory) if rssObject.statusCallback ~= nil then local status = {event = "RSSDownloaded", stories = #rssObject.stories} rssObject.statusCallback(status) end for i = 1, #rssObject.stories do parseStory(rssObject, rssObject.stories[i], i) end end function parseStory(rssObject, story, index) story.rssImage = "rssImage" .. index .. ".png" network.download( story.mediaContent, "GET", function(event) onImageDownloaded(event, rssObject, story) end, story.rssImage, system.DocumentsDirectory ) end function onImageDownloaded(event, rssObject, story) if event.isError then if rssObject.errorCallback ~= nil then rssObject.errorCallback() end else local status = {event = "RSSImageDownloaded"} rssObject.statusCallback(status) end end |
So now that we've created the RSS Downloader module, we can test it out. The following code shows how to use the RSS Downloader module (keep in mind this isn't in the downloadable project. It's simply a test case):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | local rssDownloader = require("rssDownloader") local flickrUrl = "http://api.flickr.com/services/feeds/photos_public.gne?lang=en-us&format=rss_200&tags=nature" local mainGroup = display.newGroup() local downloader = rssDownloader.new() rssDownloader.downloadFeed(downloader, flickrUrl, onFeedDownloadStatus, onFeedDownloadError) function onFeedDownloadStatus(status) if status.event == "RSSDownloaded" then print("Downloading " .. status.stories .. " images") mainGroup.numStories = status.stories mainGroup.curStoryIndex = 0 else mainGroup.curStoryIndex = mainGroup.curStoryIndex + 1 if mainGroup.curStoryIndex == mainGroup.numStories then print("Download complete!") end end end function onFeedDownloadError() print("Error downloading feed!") end |
At this point, our app now has the ability to download an RSS feed, parse it and download any associated images for that feed. What we now have to do is create the user interface for the application to display the data that we downloaded. I'll first go through each custom control that was created for the app and then tie it all together in our main module to conclude our discussion.
This application uses a few custom controls in order to display information to the user. Rather than place everything in main.lua I try to create modules that handle the main work. The first screen the user is presented with is the category grid. So let's start with that.
The category grid is a 4x3 grid of icons that display a category the user can search on Flickr with. The code itself is rather simple so it doesn't warrant much discussion. When you instantiate the control, you pass in a table called categories. Each element in that table contains the name of the category and the icon to use. The category grid will then loop through the table and create the necessary icons making sure everything is lined up and tidy. Also, in order to know which category the user selected, a callback method is passed to the category grid. The callback method will be called when the user touches one of the icons in the grid. The full source to the category grid is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | module(..., package.seeall) local cButtonPadding = 32 local cButtonPaddingTop = 40 local cButtonWidth = 64 local cButtonHeight = 64 local maxColumns = 4 function new(categories, selectedCallback) local categoryGridGroup = display.newGroup() for i = 1,#categories do local gridButton = createGridButton(categories[i], selectedCallback) gridButton:setReferencePoint(display.TopLeftReferencePoint) gridButton.x = (((i - 1) % maxColumns) * cButtonWidth) + (((i - 1) % maxColumns) * cButtonPadding) gridButton.y = math.floor((i - 1) / maxColumns) * cButtonHeight + math.floor((i - 1) / maxColumns) * cButtonPadding + cButtonPaddingTop local gridButtonText = display.newText(categories[i].name, 0, 0, native.systemFont, 16) gridButtonText.x = gridButton.x + cButtonWidth / 2 gridButtonText.y = gridButton.y + cButtonHeight + gridButtonText.height / 2 categoryGridGroup:insert(gridButton) categoryGridGroup:insert(gridButtonText) end categoryGridGroup.title = display.newText("Select a category", 0, 0, native.systemFont, 32) categoryGridGroup.title:setReferencePoint(display.CenterReferencePoint) categoryGridGroup.title.x = categoryGridGroup.width / 2 categoryGridGroup.title.y = categoryGridGroup.title.height / 2 categoryGridGroup:insert(categoryGridGroup.title) categoryGridGroup.x = (display.contentWidth - categoryGridGroup.width) / 2 return categoryGridGroup end function createGridButton(category, selectedCallback) local gridButton = display.newImage(category.image) gridButton.category = category gridButton.selectedCallback = selectedCallback gridButton.xScale = cButtonWidth / gridButton.width gridButton.yScale = cButtonHeight / gridButton.height gridButton:addEventListener("touch", function(event) onGridButtonTouched(event, gridButton) end) return gridButton end function onGridButtonTouched(event, gridButton) if event.phase == "ended" then gridButton.selectedCallback(gridButton.category) end end |
It should be noted that a few "constants" are found at the top of the file. These can be changed to suit your application should you deem it necessary.
When the application is downloading images from Flickr, the app may appear to hang from the users' point of view. A progress bar can be used in cases like this to let them know that something is actually happening. The progress bar presented here was a quick and dirty control that I created in less than an hour. To use it in your application, you may need to customize it further to suit your needs. The way it works is also rather simple. A new image is created with a width of 1. The total units are passed in as an argument to its new() method so that it can calculate how far to increment itself whenever it is told to increment. When the increment method is called, it simply uses the original image and modifies its width. Here's a screenshot of it in action (you can see the progress bar at the bottom of the screen):

Here's the progressBar source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | module(..., package.seeall) function new(totalUnits, x, y, totalWidth) local progressBarGroup = display.newGroup() progressBarGroup.totalUnits = totalUnits progressBarGroup.totalWidth = totalWidth progressBarGroup.curUnit = 0; progressBarGroup.progressBar = display.newImage("progressBar.png", x, y) progressBarGroup.progressBar.width = 1 progressBarGroup.progressBar:setReferencePoint(display.CenterLeftReferencePoint) progressBarGroup.progressBar.x = x progressBarGroup.progressBar.y = y progressBarGroup:insert(progressBarGroup.progressBar) return progressBarGroup end function increment(progressBarGroup) local curX = progressBarGroup.progressBar.x progressBarGroup.curUnit = progressBarGroup.curUnit + 1 progressBarGroup.progressBar.width = (progressBarGroup.totalWidth / progressBarGroup.totalUnits) * progressBarGroup.curUnit progressBarGroup.progressBar:setReferencePoint(display.CenterLeftReferencePoint) progressBarGroup.progressBar.x = curX end |
The title bar is yet another simple control. It's job is to simply display the title of the image current being shown to the user. It contains the obligatory new method which returns a new display group allowing the caller to fine tune any display object aspects of the control. To add/change the title, the caller simply has to call setTitle passing in the display group returned from new and a string for the title. Again, it's pretty basic so feel free to add onto this control to make it better. You could add such things as allowing the caller to specify the font and/or the font size or even use a different background image. Here's a screenshot of the title bar in action followed by the title bar code itself.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | module(..., package.seeall) function new() local titleBarGroup = display.newGroup() return titleBarGroup end function setTitle(titleBarGroup, title) if titleBarGroup.titleGroup ~= nil then display.remove(titleBarGroup.titleGroup) end titleBarGroup.titleGroup = createTitleGroup(title) titleBarGroup:insert(titleBarGroup.titleGroup) end function createTitleGroup(title) local titleGroup = display.newGroup() local titleBackground = display.newImage("tabBar.png", 0, 0) titleBackground.alpha = 0.5 titleGroup:insert(titleBackground) if title == nil then title = "No Title" end local title = display.newText(title, 0, 0, native.systemFont, 24) title:setReferencePoint(display.CenterReferencePoint) title.x = display.contentWidth / 2 title.y = titleBackground.height / 2 titleGroup:insert(title) return titleGroup end |
The last control that deserves some explanation is the dynamic image slider. You may be wondering why it's called a "dynamic" image slider. When I first started my original application (the one for a friend of mine) I needed an image slider that would allow you to swipe back and forth to view a collection of images. I did find a good one on Code Exchange but there was one thing I didn't like about it. It loaded all the images necessary for its operation when it was created. Knowing that there could potentially be a lot of images that I needed to show, that just wasn't feasible for my application due to memory contraints. What I needed was a way for the image slider to accept as input a list of filenames and allow it to dynamically load and unload images as the user used the control. Thus, the Dynamic Image Slider control was born.
The Dynamic Image Slider control loads 3 images at a time. Obviously it loads the image the user is currently looking at but it also loads the image that occurs before that current image and the image that immediately follows it. When the user swipes forward to look at the next image, the "current" image becomes the new "previous" image and the "previous" image is unloaded since it is no longer needed (I sure hope that sentence made sense). Likewise, if the user swipes backwards, the "current" image becomes the "next" image and the image that was the "next" image is unloaded. So you see, only 3 images are ever loaded at one time.
So let's look at some dynamic image slider code. I think the best way to go about this is to talk about each method and what it does. Most of this is pretty self-explanatory and you should have no problem retro-fitting this into your own code. Let's start.
Just like I mentioned at the beginning of this article, every control that I makes has a new method that I create. In this case, I'm also passing in a table containing the RSS stories that were parsed out into a nice LUA table for us. The dynamic slider control will use that information to build itself. As you walk through it line by line you can see that it first creates some necessary display groups. It then creates some expando properties that will be used for the 3 images that it needs to create each time the user swipes the control (prevImage, curImage and nextImage). It then saves the stories table since it will be accessing it as the user uses the control. This is followed up by creating a simple background rectangle and adding it to the display group. We create the title bar control next and add it to the group. Note that the title bar is initially hidden. This control will allow the user to display or hide the title bar with a simple tap on the image itself. We create the necessary image objects next (more on that later) and finally follow it all up by adding a touch listener to handle the interaction between the user and our control.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | function new(stories) local sliderGroup = display.newGroup() local sliderImagesGroup = display.newGroup() sliderGroup.imageContainer = sliderImagesGroup sliderGroup.prevImage = nil sliderGroup.curImage = nil sliderGroup.nextImage = nil sliderGroup.stories = stories sliderGroup.curStoryIndex = 1 sliderGroup.background = display.newRect( 0, 0, display.contentWidth, display.contentHeight ) sliderGroup.background:setFillColor(0, 0, 0) sliderGroup:insert(sliderGroup.background) sliderGroup:insert(sliderGroup.imageContainer) sliderGroup.titleBar = titleBar.new() titleBar.setTitle(sliderGroup.titleBar, sliderGroup.stories[1].title) sliderGroup.titleBar.isVisible = false sliderGroup:insert(sliderGroup.titleBar) createImages(sliderGroup) sliderGroup.background:addEventListener("touch", function(event) onSliderTouched(event, sliderGroup) end) return sliderGroup end |
When the control is first initialized it needs to create the initial 3 images that it uses. That's where createImage and its helper method getStoryImage comes in. These 2 methods work together to create our display.newImage objects and position them properly on the screen. It should be noted that only 1 image is viewable in the default state. sliderGroup.prevImage is positioned off-screen and to the left of the main image and sliderGroup.nextImage is positioned off-screen and to the right of the main image. When the user swipes on the main image, we adjust the x positions of these 3 images to give it that slider effect (code for the touch interaction is shown later). So, the code below simply creates our 3 images, positions them properly and inserts them into our main image container display group.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | function createImages(sliderGroup) cleanImages(sliderGroup) sliderGroup.prevImage = getStoryImage(sliderGroup, sliderGroup.curStoryIndex - 1, -display.contentWidth / 2, display.contentHeight / 2) sliderGroup.curImage = getStoryImage(sliderGroup, sliderGroup.curStoryIndex, display.contentWidth / 2, display.contentHeight / 2) sliderGroup.nextImage = getStoryImage(sliderGroup, sliderGroup.curStoryIndex + 1, display.contentWidth + display.contentWidth / 2, display.contentHeight / 2) sliderGroup.imageContainer:insert(sliderGroup.prevImage) sliderGroup.imageContainer:insert(sliderGroup.curImage) sliderGroup.imageContainer:insert(sliderGroup.nextImage) end function getStoryImage(sliderGroup, index, x, y) if index < 1 then index = #sliderGroup.stories elseif index > #sliderGroup.stories then index = 1 end local image = display.newImage(sliderGroup.stories[index].rssImage, system.DocumentsDirectory) image.xScale = display.contentWidth / image.width image.yScale = display.contentHeight / image.height image.x = x image.y = y return image end |
Now we can focus on user interaction with our image slider. There are different states to user interaction that we need to account for and these states are found in the event.phase string that gets passed to our method. The event.phase is either "began", "moved", "ended" or "cancelled". In the "began" phase we set focus to our control so we capture all the touch events in our app. We also save the starting x and y position of the actual touch event itself. This will help us calculate how far the user has moved their finger during the process (the delta).
When the user moves their finger across our control, we get the "moved" event.phase. In this case we calculate the delta value to see how far they've moved from their last known position which we then update on the next line by setting sliderGroup.prevPos to the event's x location. The last step in this phase is to update the x locations of our 3 image objects. This is what gives us our slider effect.
Finally, the user will eventually have to lift their finger off of the screen. When this happens, we need to calculate how far they travelled from their initial starting position to their final ending position. This is known as the dragDistance. We key off this value to either detect a really small movement which we interpret as a "tap" or a longer distance which we use to set focus to one of our 3 images. By setting focus, I mean that we have to center either the sliderGroup.prevImage, the sliderGroup.curImage or the sliderGroup.nextImage. We will create 3 methods to perform this "focus" behavior for us: focusOnCurImage, focusOnPrevImage and focusOnNextImage. If the user didn't move that far however, we just simply toggle the isVisible property of our title bar control using the not Lua keyword to toggle it for us.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | function onSliderTouched(event, sliderGroup) if event.phase == "began" then display.getCurrentStage():setFocus( sliderGroup.background ) sliderGroup.background.isFocus = true sliderGroup.startPos = event.x sliderGroup.prevPos = event.x elseif sliderGroup.background.isFocus then if event.phase == "moved" then local delta = event.x - sliderGroup.prevPos sliderGroup.prevPos = event.x sliderGroup.curImage.x = sliderGroup.curImage.x + delta sliderGroup.prevImage.x = sliderGroup.prevImage.x + delta sliderGroup.nextImage.x = sliderGroup.nextImage.x + delta elseif ( event.phase == "ended" or event.phase == "cancelled" ) then dragDistance = event.x - sliderGroup.startPos if dragDistance > -10 and dragDistance < 10 then focusOnCurImage(sliderGroup) sliderGroup.titleBar.isVisible = not sliderGroup.titleBar.isVisible elseif dragDistance < -100 then focusOnNextImage(sliderGroup) elseif dragDistance > 100 then focusOnPrevImage(sliderGroup) else focusOnCurImage(sliderGroup) end display.getCurrentStage():setFocus( nil ) sliderGroup.background.isFocus = false end end end |
When the user lifts their finger from our control, we have to center one of the images. If they travelled a long distance from right to left, we will set focus to the sliderGroup.prevImage. Likewise if they travelled a long distance swiping from left to right, we set focus to the sliderGroup.nextImage. We'll look at the code just for focusing on the previous image since the code for focusing on the next and current image is similar.
The first thing we do when we want to focus on the previous image is to remove the sliderGroup.nextImage since that image is no longer needed and will be replaced by a different image (precisely the sliderGroup.curImage becomes the new sliderGroup.nextImage). The next operation we do are a couple of transitions (so the movement isn't so jarring to the user) to move the sliderGroup.curImage to the sliderGroup.nextImage location and the current sliderGroup.prevImage to the location where sliderGroup.curImage was located. At this point, we have 2 images occupying the current image location and the next image location but nothing in the previous image location. After we update our story index (wrapping the index around if we need to) and updating the title bar, we do a little object management with our display group. The sliderGroup.curImage becomes the new sliderGroup.nextImage and the sliderGroup.prevImage becomes the new sliderGroup.curImage. Finally (if I haven't lost you yet) we need to create a new sliderGroup.prevImage which is exactly what the createPrevImage method does.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function focusOnPrevImage(sliderGroup) display.remove(sliderGroup.nextImage) transition.to(sliderGroup.curImage, {time = 400, x = display.contentWidth + display.contentWidth / 2, transition = easing.outExpo }) transition.to(sliderGroup.prevImage, {time = 400, x = display.contentWidth / 2, transition = easing.outExpo }) sliderGroup.curStoryIndex = sliderGroup.curStoryIndex - 1 if sliderGroup.curStoryIndex < 1 then sliderGroup.curStoryIndex = #sliderGroup.stories end titleBar.setTitle(sliderGroup.titleBar, sliderGroup.stories[sliderGroup.curStoryIndex].title) sliderGroup.nextImage = sliderGroup.curImage sliderGroup.curImage = sliderGroup.prevImage createPrevImage(sliderGroup) end |
Ok, time to bring this plane down for a landing. So far we covered basic module design (or at least 1 way of doing it), creating an RSS Downloader object and creating several different custom controls that our application will need. What's left is to put all these pieces together in main.lua. The flow of the application dictates how we create our controls and at what time. When the application first starts, we display our Category Grid control. When the user selects a category we create our RSS Downloader object and our Progress Bar control. When we receive the event that the download is finished we display our Dynamic Image Slider control.
Let's look at the initial application startup. Our init method will first create a background image and add that to our main display group. We then create a table listing several different categories. These categories are passed to the Category Grid which we create and add to our main display group. No you can see how advantageous it is to create custom controls that you can just drop into your application at any point with a minimum amount of code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | function init() mainGroup.categorySelected = false mainGroup.backGround = display.newImage( "grass.png" ) mainGroup:insert(mainGroup.backGround) local categories = { { name = "Corona", image = "icons/corona.png" }, { name = "Zombies", image = "icons/zombie.png" }, { name = "Video Games", image = "icons/Pacman.png" }, { name = "Nature", image = "icons/nature.png" }, { name = "Seattle", image = "icons/seattle.png" }, { name = "Lua", image = "icons/lua.png" }, { name = "Football", image = "icons/football.png" }, { name = "Politics", image = "icons/politics.png" }, { name = "Love", image = "icons/love.png" }, { name = "Candy", image = "icons/candy.png" }, { name = "Technology", image = "icons/technology.png" }, { name = "Weather", image = "icons/weather.png" } } mainGroup.categoryGrid = categoryGrid.new(categories, function(category) onCategorySelected(category) end) mainGroup:insert(mainGroup.categoryGrid) end |
With our Category Grid displaying the user can now select a category which we will then receive in the callback method we passed to the Category Grid when we created it. The method, onCategorySelected will simply instantiate our RSS Downloader object and download the feed.
1 2 3 4 5 6 7 | function onCategorySelected(category) if mainGroup.categorySelected == false then mainGroup.categorySelected = true mainGroup.rssDownloader = rssDownloader.new() rssDownloader.downloadFeed(mainGroup.rssDownloader, baseUrl .. category.name, onFeedDownloadStatus, onFeedDownloadError) end end |
During the process of downloading the RSS feed and its related images, the RSS Downloader will notify us via callbacks of what is happening. The first event we will get is the "RSSDownloaded" event. This tells us that the feed has finished downloading and that it will start to download the related images. We know how many images it has to download based on the number of stories it tells us it found, specified in the status.stories property that it passed to us. Since we received that value, we can go ahead and create our Progress Bar control and tell it how many total units it will be using when it's incrementing.
If the status.event we receive isn't an "RSSDownloaded" event, we know that it has just finished downloading an image. We keep track of how many times this happens so we know that once we've reached a certain total all of our images have been downloaded and we can create our Dynamic Image Slider control. So we first increment our Progress Bar, check to see if the total amount has been reached and if it has remove any controls we don't need anymore, instantiate our Dynamic Image Slider control and let it take control from here on out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function onFeedDownloadStatus(status) if status.event == "RSSDownloaded" then mainGroup.progressBar = progressBar.new(status.stories, 0, display.contentHeight - 5, display.contentWidth) mainGroup:insert(mainGroup.progressBar) mainGroup.numStories = status.stories mainGroup.curStoryIndex = 0 else mainGroup.curStoryIndex = mainGroup.curStoryIndex + 1 progressBar.increment(mainGroup.progressBar) if mainGroup.curStoryIndex == mainGroup.numStories then display.remove(mainGroup.categoryGrid) display.remove(mainGroup.progressBar) display.remove(mainGroup.backGround) mainGroup.imageSlider = dynamicSlider.new(rssDownloader.getStories(mainGroup.rssDownloader)) mainGroup:insert(mainGroup.imageSlider) end end end |
Whew! This turned out to be a long article and I hope you stuck through it. For old-timers there probably wasn't anything you already didn't know. For the newbies, I hope you learned a few tricks here and there. The Flickr Photo Browser is semi-complex and for someone that's new to Lua and/or Corona it may seem daunting. My goal for this article was to peel away the layers so you see how it all works. I encourage you to download the source and take a crack at modifying portions of it yourself.
Feel free to email me at anytime or comment on this page and I'll get back to you as quick as I can.
Very nice .. Thank you.
Thank you, thank you.
I've steered clear of rss or xml data feeds, but your code has made me want to explore it more and demonstrates it brilliantly.
Very well written and very comprehensive.
The level and tone of the article was spot on. (I'm a newbie with one app on the itunes store and 2 in develompment).
Thanks for sharing.
Nick
I downloaded the code and just gave it a run before going through all the items.. I made "no" changes. However Im getting errors..
Update -- ***** I went and created the titleBar.lua *** Guess it was missing in your project**
Very nice. Thanks you for the article
WARNING: Disabling the idle timer reduces battery life on the device. Also, there is no timer on the simulator
Runtime error
...epunk-s-Code-8537252/FlickrBrowser/dynamicSlider.lua:3: module 'titleBar' not found:resource (titleBar.lu) does not exist in archive
no field package.preload['titleBar']
no file '/Users/zoid66/Desktop/codepunkschmidt-Codepunk-s-Code-8537252/FlickrBrowser/titleBar.lua'
no file '/Applications/CoronaSDK/Corona Simulator.app/Contents/Resources/titleBar.lua'
no file '/Applications/CoronaSDK/Corona Simulator.app/Contents/Resources/titleBar.blu'
stack traceback:
[C]: ?
[C]: in function 'require'
...epunk-s-Code-8537252/FlickrBrowser/dynamicSlider.lua:3: in main chunk
[C]: in function 'require'
...hmidt-Codepunk-s-Code-8537252/FlickrBrowser/main.lua:5: in main chunk
Runtime error: ...epunk-s-Code-8537252/FlickrBrowser/dynamicSlider.lua:3: module 'titleBar' not found:resource (titleBar.lu) does not exist in archive
no field package.preload['titleBar']
no file '/Users/zoid66/Desktop/codepunkschmidt-Codepunk-s-Code-8537252/FlickrBrowser/titleBar.lua'
no file '/Applications/CoronaSDK/Corona Simulator.app/Contents/Resources/titleBar.lua'
no file '/Applications/CoronaSDK/Corona Simulator.app/Contents/Resources/titleBar.blu'
stack traceback:
[C]: ?
[C]: in function 'require'
...epunk-s-Code-8537252/FlickrBrowser/dynamicSlider.lua:3: in main chunk
[C]: in function 'require'
...hmidt-Codepunk-s-Code-8537252/FlickrBrowser/main.lua:5: in main chunk
***** I went and created the titleBar.lua *** Guess it was missing in your project**
zoid66: Whoops. I've added the file to the repository so it should be good to go now.
woa , thanks for sharing . anw , your pic r so nice :)
Wow! All 3 projects and your write up are fantastic -- thanks for sharing!
thanks!!!
Very Nice!!! Been looking for this for a couple of days now... Thank you much.
Just curious, could this be added to the storyboard?
:)
wow !
thank you for sharing
.c