Here is a simple way to index art and sound resources for your application. Use case: I find myself manually typing file names into a plist, so that the app can load certain images/sounds into memory. Also, when I add files in the future, I don’t want to add them manually to the plist (or I might forget to add them and get unexpected behavior). So here is the init method for a class (a singleton in this case), that searches the app bundle for specific resources. The class is called SpriteLoader:
- (id)init {
if ( self = [super init] ) {
// get the application bundle, then put all the filenames from the desired subdirectory into an array (see note about subdirectories)
NSArray *artFileNames = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:@"GeneralSpriteList"]; // returns NSArray of NSString objects
// optional code: if NSBundle takes a long time to find files, you might want to only search once ever, once per launch, once per app update, etc.
// NSUserDefaults * theDefaults = [NSUserDefaults standardUserDefaults];
// [theDefaults setObject:artFileNames forKey:@"GeneralSpriteList"];</code>
// now init our class dictionary. For this class, we want it to contain actual image files for the strings we collected, plus reasonably intelligible keys.
sprites = [[NSMutableDictionary alloc] init];
int index = [artFileNames count] - 1; // last index + 1
if (index == 0) { DLog(@"WARNING: Nothing in sprite array! Crash in 3...2...1...Aaaaaa!") }
while (index > 0) {
index -- ;
UIImage *thisSpriteImage = [UIImage imageWithContentsOfFile:[artFileNames objectAtIndex:index]];
// now cut out the file url or path to get just the filename
NSString *thisString = [[artFileNames objectAtIndex:index] lastPathComponent];
// store the actual image in the "sprites" dictionary, with key of same name BUT without the file extension
[sprites setObject:thisSpriteImage forKey:[thisString stringByDeletingPathExtension]];
}
}
return self;
}
Here’s some debugger output to show it is working:
(gdb) po sprites
{
"weapons-01" = "<UIImage: 0x11be70>";
"weapons-02" = "<UIImage: 0x11be71>";
"weapons-03" = "<UIImage: 0x11be72>";
}
Careful, this dictionary could become violent if provoked.
Now whenever I need a texture file from [[SpriteLoader sharedSpriteLoader] sprites] , I will know that I have the most up-to-date collection of sprites :) There are lots of other things that can be done from here. For example, if you had a long animation, you could place all the frames in a directory and let NSBundle index them for you. Make sure the strings are sorted, save an array of sorted paths to defaults (perhaps only the first time the program loads), and use it to load the array that iterates through your animation frames.
What we’ve done here is intended for collections of art and sound resources, but can be extended to support pre-indexed atlas sprites, or perhaps composite sound files.
By the way, I’m really sorry about how the code formats in my WP editor. I will try to fix this in the future.
Now… NOTE ABOUT SUBDIRECTORIES: If you use XCode, the groups you see in the IDE do not correspond to directories in the app bundle. The solution is to drag in a folder, and check the radio button stating: “Create Folder References for any added folders“. Once done, this will cause a blue, not yellow, folder to manifest itself in XCode. Files in that folder will be in a subdirectory of same name in your app bundle! If you don’t do this, the files will be at the top of the bundle and you won’t be able to use the technique described in this post.
EDIT: And further note about folders… It is worth noting that NSBundle, UIImage imageNamed: , and others might not find your files once you take control of directory structure. It’s useful if you’re indexing all the images as in the post above, but it would be annoying if you frequently use:
UIImage *theImage = [UIImage imageNamed:@"myImage.png"];
because UIImage would most likely return nil. In this case, you have to be more specific:
UIImage *theImage = [UIImage imageNamed:@"MyGreatDirectory/myImage.png"];
for UIImage to return the file you want. Here’s some GDB output with further examples of what does and does not work once you take control of the directory structure:
// doesn't work
(gdb) po [[NSBundle mainBundle] pathForResource:@"weapons-03" ofType:@"png"]
Can't print the description of a NIL object.
// works
(gdb) po [[NSBundle mainBundle] pathForResource:@"GeneralSpriteList/weapons-03" ofType:@"png"]
/var/mobile/Applications/06CDAEF9-040B-4AF1-9676-C13AEC1C96AB/MyViolentApp.app/GeneralSpriteList/weapons-03.png
// doesn't work
(gdb) po [UIImage imageNamed:@"weapons-03.png"]
Can't print the description of a NIL object.
// works
(gdb) po [UIImage imageNamed:@"GeneralSpriteList/weapons-03.png"]
<UIImage: 0x11be72>
(gdb) ...