【XCode中变更 resource文件后,需要重新编译才能正确识别更新】问题的解决方案



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.


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!