views:

643

answers:

4

I have an iPhone application that comes in differently skinned flavors, with different art assets and sounds, but all the same code.

I've got things set up with multiple targets, but the problem I'm having is I have to have a different set of UIBuilder nib files, one per view per target, set up to point to the correct art for that target.

This is a little frustrating, because if I make a change to one nib file, I have to manually make the same changes, same connections, etc in all the other nib files. I also put all the assets in with different names for the different skins as well, so they don't collide in the project. So, if I have targets A and B, I have a_main_menu.png, b_main_menu.png... a_FooViewController.xib, b_FooViewController.xib... etc.

Is there a way to make a nib file that points to assets that have the same name, but are in... umm, different bundles? is that what a bundle is for? I can imagine fixing something like this in my code (probably search and replace A for B in the nib before loading it could even be good enough), though I haven't tried it, that's ugly.

This was a manageable strategy for my first set of applications (though far from optimal), but as my stuff gets more complex, it's getting REALLY hard to keep my builder files in sync, and it's just not a very DRY way to work. Is there a better way aside from ditching uibuilder and creating my views in code?

It would be nice if this worked at the xcode/builder level, so builder would respect my current target and show the art for that target while I'm working... but I can maybe live without that, like if I could select the current set of art at run time, and I'd only be able to work with one set in builder. I could also do it with one set of nibs by having a single target and manually replacing all the art before building, but that's not very nice either.

Best of all would be if I could mix the two strategies - like if I have one target that has one view out of several that just HAS to be laid out differently... but that's optional.

Am I asking the right question?

Thanks!

+1  A: 

Assuming that each app only needed one skin at a time, and that you are creating multiple apps with different themes, I would create a branch in my source control and then replace the necesary assets. You keep a master branch with all default assets and then you merge code changes from the master into your skinned branches keeping everything in sync.

Git makes this super easy.

Squeegy
A: 

I think the best option is to use something like git.

If you used git, you can have a main branch that holds the master source code and logic, and have sub branches that have different sets of data/images for each particular flavour of your application. In this solution, XCode wouldn't even know what's going on.

Jessedc
A: 

I think this is what viewDidLoad is for, to customize more, in here you can change the images path.

CiNN
+1  A: 

One way to do this is to move all your artwork into a separate bundle and dynamically load it at runtime (sort of like a "media plug-in").

  • Create a separate project and choose "Mac OS X / Framework & Library / Bundle." Call it, say, media.bundle.

  • Move all your artwork into this project.

  • To make life easy, add an extra "Run Script" step to the target copying the built output from the build directory over to a subfolder under your main project. To make it even easier, add the media bundle as a dependent project in your main project so it gets rebuilt automatically.

In your main project you'll need these two methods. You can make them static methods under a BundleUtils class:

+ (NSString *) bundleDirectoryFor:(NSString *) bundleName
{
 NSString* basePath = [[[NSBundle mainBundle] resourcePath]
     stringByAppendingPathComponent:[NSString
                                stringWithFormat:@"%@.bundle", bundleName]];
 return [[NSBundle bundleWithPath:basePath] resourcePath];
}

+ (NSString *) resourceInBundle:(NSString *)bundleName 
                       fileName:(NSString *)fileName
{
 NSString *bundlePath = [BundleUtils bundleDirectoryFor:bundleName];
 return [bundlePath stringByAppendingPathComponent:fileName];
}

Now every time you want to access artwork in your bundle, you can get the path to the right file with:

NSString* imageFile = [BundleUtils resourceInBundle:@"media.bundle" 
                                        fileName:@"image.jpg"];
UIImage* image = [UIImage imageNamed:imageFile];

Obviously, the name "media.bundle" can be substituted at runtime for a different one so you can switch to other bundles based on your app's needs. This is also a handy way to support downloadable content (say, as for-pay add-ons).

One caveat: this assumes you need to load images dynamically at runtime via code. But you also have static NIB files that have media embedded in them. You'll have to figure out a way to make the static NIB file use the dynamic methods to resolve media file names. One way is to use MethodSwizzling and watch for filepaths with a prefix of type, say, "media:image.png" and redirect those to use the BundleUtil methods.

Another way is to do your layout with IB then convert it to Obj-C code using nib2objc and then substitute the plugin bundle mechanism.

Hope this helps.

Ramin