7

I'd like to make my iPhone app to be able to switch between skins (or design theme, or look and feel, such as wooden, metal, earth color, men's, girls, etc...).

I'll prepare some sets of skins that contains images for buttons and backgrounds, sounds, and text color, and let the user decide which set of skin they want to use by the application settings.

What is the best practice to implement this?

The conditions are:

  • I'd like to use Interface Builder
  • I need to support iOS 3.1.3 and later
  • I want to make the sets of skins downloadable from the internet (I can't bundle all the skins in the app, as one set of skin requires lots of images and the app file size could become huge if I do so... I also don't want to hardcode any information about specific skins.)
  • If a custom skin does not contain one or some elements, (such as an image or sound file), I want it to use the missing element from the default set of skin.
  • I don't want to create Nib files for each skin. The Nib file for one screen should be the only one in the main bundle for easier maintenance.

I'm thinking about making a superclass of all the UIViewControllers in my app and override the part that it loads Nib file, and instead of loading from the main bundle, load the resources from the skin that is saved in the Document directory... but I don't know how to do it... The default behavior of the Nib-loading methods always loads resources from the main bundle and the information about resource file names are lost after reading... :(

Thanks in advance for your help.

Taka
  • 1,334
  • 17
  • 24
  • 1
    There is a very thorough explanation on how to skin an iOS app here: http://stackoverflow.com/questions/8919334/how-to-create-multiple-themes-skins-for-iphone-apps?rq=1 It also explains how to skin a nib, esp. from iOS 5. – Yuri Shkuro Jan 24 '14 at 05:05
  • 1
    You can have a look at the below library https://github.com/charithnidarsha/MultiThemeManager – Charith Nidarsha May 31 '14 at 07:06

2 Answers2

4

Am not sure about best practice .. But, if your app is not big enough, then a well structured plist is your friend.

Initially, you could choose: Metal Theme. The following should hold:

You either have a Singleton ThemeManager, or just stick an NSDictionary to one of your Singletons if appropriate.

The point behind the ThemeManager is the mapping between the asset and the theme..

Some sample code (written directly on SOF .. Don't mind Syntax mistakes):

#define kThemeMap(__x__) [[ThemeManager sharedManager] assetForCurrentTheme:__x__]

...

-(void)doUselessStuff {
    UIImage* backgroundImage = [UIImage imageNamed:kThemeMap(@"FirstViewBG")];

    ...

}

//in the ThemeManager:
//returns the appropriate name of the asset based on current theme
-(NSString*)assetForCurrentTheme:(NSString*)asset {
    //_currentTheme is an NSDictionary initialized from a plist. Plist can be downloaded, too.
    NSString* newAsset = [_currentTheme objectForKey:asset];
    if(newAsset == nil) {
        newAsset = [_defaultTheme objectForKey:asset];
    }
    return asset;
}

//Let us assume the user selects Metal Theme somewhere .. Still coding ThemeManager:
-(void)selectedNewTheme:(NSString*)newTheme {
    //First, get the full path of the resource .. Either The main bundle, or documents directory or elsewhere..
    NSString* fullPath = ...;
    self.currentTheme = [NSDictionary dictionaryWithContentsOfFile:fullPath];
}

The plist files are just a dictionary with string to string mapping... something like this:

//Default.plist
@"FirstViewBG"  : @"FirstViewBG_Default.png"
@"SecondViewBG" : @"SecondViewBG_Default.png"
@"WinSound"     : @"WinSound_Default.aiff"

//Metal.plist
@"FirstViewBG"  : @"FirstViewBG_Metal.png"
@"SecondViewBG" : @"SecondViewBG_Metal.png"
@"WinSound"     : @"WinSound_Metal.aiff"

Alternatively, you can just save the postfix, if that is good enough for you.. But, it will require string manipulation, by slicing the extension -> adding the postfix -> adding the extension ..

Or maybe make it a prefix?

Mazyod
  • 22,319
  • 10
  • 92
  • 157
  • Thank you very much for you answer and the very descriptive code! This could be a possible solution. :) – Taka Oct 29 '11 at 23:47
  • Only reason I hesitate to take this approach is that in this way I have to hold redundant information about the resource file name both in the Nib file and the Plist. It won't cost much though... I'm still hoping that there is a way to read the resource file names during loading the Nib, and add the theme-specific postfix or prefix to the filename in the process... Any idea? If it's not easy or not possible, I will take this approach. Thanks. ;) – Taka Oct 29 '11 at 23:58
  • aah .. I can't really think of a way to do that. You must link them as IBOutlets and customize them in the viewDidLoad ;) – Mazyod Oct 30 '11 at 10:12
  • 1
    Thanks Mazyod! Looks like there is no easy way to read the resource file name from the Nib, so I decided to take your approach. Instead of making IBOutlets for all the UIView elements, I set integer IDs in tag property, and used it in the PList as the Key for resource file, then created a common UIViewController parent class for all of my app UIViewController subclasses and overrode viewWillAppear to search in the view hierarchy recursively with the IDs to set the image resources. Although this leaves some redundancy, still works fairly well. Thanks! ;) – Taka Oct 31 '11 at 00:35
  • Np, although I feel you figured out the main part yourself :) – Mazyod Oct 31 '11 at 03:05
0

You can category on UIImage with the methored imageNamed:, use the custom imageNamed: instead of the default one. In the custom methord, selected the image by theme.

user501836
  • 1,106
  • 1
  • 12
  • 20
  • Thanks for your reply. Well, it will work, but I believe that it's not a good practice to use Objective-C Category for overriding a method. (As discussed in http://stackoverflow.com/questions/5272451/overriding-methods-using-categories-in-objective-c) In that case, we may subclass UIImage to override imageNamed:, but this way we cannot use Interface Builder effectively. – Taka Mar 21 '13 at 07:14