Xcode resource groups and folder references when building for iPhone

Written by David Frampton @ 7:17 am, May 2, 2009

[Update: Joachim Bengtsson provided a much much easier way to make Xcode notice new changes, the new simpler script is now included in the instructions. Thanks Joachim!]

If you don’t see a problem with the way that Xcode manages ‘Resources’ (images, sounds etc.) then you probably have nothing to learn from this article. Xcode usually manages just fine if you only have a small project, with limited changes to resources, and only a couple of applications and one person ever modifying them.

But, Xcode’s directory management is broken enough to become really annoying in larger projects. I’ll explain why, as well as offer a solution. This article focuses on iPhone development. A very similar problem exists when developing for Mac OS X, with a similar but simpler solution, but I won’t go into it here.

Usually when you want to have an image or sound or any other resource in your iPhone application, you add it to Xcode, it is silently added to the ‘Copy Bundle Resources’ build phase, it might get converted or stripped back (pngs get quite heavily modified) and then gets copied into the app bundle. You can then access it at runtime with NSBundle methods. Easy.

The problem only shows up when you want to have some kind of directory structure on disk. There are many reasons why you might want to do this. In a game, perhaps your levels are themselves directories. Perhaps you want to switch between different versions of resources, or simply like to be a little more organized than having all of your resources at one level in one big-ass directory.

Xcode offers two options when you add a directory to the project. You can either add it as the default yellow ‘group’ or as a blue ‘folder reference’. The problem is, that both have pretty major limitations (and bugs).

The group option will never notice if the directory contents are added or removed. Every time you add or remove a file or directory in the file system, you will also have to change it in Xcode. Also, the directory structure is lost when it’s copied to the iphone app, and so inside your app bundle is just a big list of all your resources in the base directory. As a result of this, duplicate filenames become an issue. If any files within your directory structure on disk contain the same filename, the build process silently screws everything up. It appears to be ‘first in wins’, with only one of the resources making it into the app bundle. So it’s no good if you have a bunch of different level packages each containing a different ‘Terrain.png’ file.

Which means you have to use the other option, blue ‘folder references’. This should be the better option of the two no matter what you are doing. When you add or remove files, it usually gets noticed. You can safely have duplicate names, and you don’t have to repeat your moving/renaming/adding etc. in Xcode. But the problem is, that when you externally change a file within a folder reference, Xcode doesn’t notice. So every time you modify an image file, you do a new build, fire it up, and think ‘It looks the same as before’. Thats because it is the same as before. You need to do a clean build (or delete the app) to force Xcode to copy the resource directory structure again.

This is a pain, and if you’re like me, you will nearly always forget, wasting large amounts of time slowly making an image darker, but it still looks too light! Half an hour later realizing it never got copied to the app bundle.

So. A solution.

I made a top level directory I called ‘GameResources’ inside of which are all the app’s resource files and sub-directories. It is important not to call this directory ‘Resources’, as it seems to confuse Xcode into giving a lot of ‘Application is already installed’ errors.

1) Add your single resource directory (named anything but ‘Resources’) to your project in the Resources section as a blue ‘Folder Reference’

Game Resources

2) Right click on your app target, select Add->New Build Phase->New Run Script Build Phase

3) In the resulting ‘Info’ window, change the shell to /bin/tcsh and copy and past the script below into the ‘Script’ text view.

Script:

touch -cm ${SRCROOT}/../../GameResources


How it should look:

Run Script Build Phase

 
4) Change the directory to your resource directory. In my case it is ‘GameResources’, a couple of levels above the source directory, but usually if your resources are located in the same directory as your Xcode project it would be simply ${SRCROOT}/MyResources

5) Change the order of the build phases (by dragging the Run Script one you just created) so that it is run just after the Copy Bundle Resources build phase. This may not be necessary, but it feels right. Leave the ‘Copy Bundle Resources’ build phase alone, it doesn’t do any harm, and is needed for distribution builds.

App Target

6) Before this, you probably used [[NSBundle mainBundle] resourcePath] or pathForResource:. These won’t work anymore, as all your resources are now inside ‘GameResources’ or whatever. [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@”GameResources”] or similar will need to be used instead.

A couple of notes – You can edit the script by double clicking the ‘Run Script’ build phase, it’s not entirely obvious. And if you have renamed your debug configuration you probably need to modify the if statement at the top of the script. 

And finally, this might not work at all for any number of reasons. I probably won’t be able to help you with any problems, but hopefully it will save someone some time and frustration. 

Maybe someone at Apple might even see this, notice the hoops we have to jump through, and be more inclined to fix the damn thing properly!









9 Comments

  1. Joachim Bengtsson

    An easier solution is to write a script that runs before the Copy Files phase, that just does a `touch [the folder reference's path on disk]`. When the folder is touched, its modification date is updated and Xcode realizes it needs to copy it.

    Comment by Joachim Bengtsson — May 2, 2009 @ 8:32 am


  2. David Frampton

    Thanks Joachim, I tested your solution and it appears to be working perfectly. Nice :) I have replaced the much more long winded solution I initially had. Thanks again.

    Comment by David Frampton — May 2, 2009 @ 8:55 am


  3. Alex Nankervis

    Thanks, this is a great post – I just switched from compiled-in resources (header files) to data bundled by Xcode and loaded at runtime. I’ve been avoiding figuring out the whole Xcode process, but this is an excellent overview.

    Comment by Alex Nankervis — May 4, 2009 @ 6:58 pm


  4. Air Master 3D v1.3 Update Submitted « Massive Ego Games

    [...] menus, less memory required to run the game, everyone wins! Speaking of resources in Xcode, there is a great blog post that overviews the whole process from getting Xcode to package your resources (reliably) to how to [...]

    Pingback by Air Master 3D v1.3 Update Submitted « Massive Ego Games — May 7, 2009 @ 3:39 pm


  5. newtoxcode

    Should it work with just adding

    touch -cm ${SRCROOT}/Resources/

    to a Run Script

    Comment by newtoxcode — June 29, 2009 @ 11:48 pm


  6. David

    Thanks for this.

    @newtoxcode: I’ve got the script working fine as follows:
    touch -cm ${SRCROOT}/data
    Where data/ is a directory in the same location as my .xcodeproj file.

    Comment by David — July 22, 2009 @ 6:33 pm


  7. Colin

    To access files in a sub-directory of resources you can go [[NSBundle mainBundle] pathForResource:@”filename” ofType:@”ext” inDirectory:@”Subfolder”], for example. Handy to not have to worry about manually building paths. We are doing this on a project with a folder references setup, as above, in the Resources folder. Works great!

    Comment by Colin — August 14, 2009 @ 2:16 pm


  8. Andres

    Thank you so much for this! I’ve been struggling with the damn clean, build for about 6 months now :( THanks again!

    Comment by Andres — September 12, 2009 @ 12:00 am


  9. GonXaS

    thank you a lot…

    it helps me so much… i was thinking that i can’t make a folder structure in the build, you taught me how…,

    thanks again!

    Comment by GonXaS — September 26, 2009 @ 3:20 am


RSS feed for comments on this post.

Sorry, the comment form is closed at this time.