web-dev-qa-db-fra.com

Comment créer plusieurs thèmes / skins pour les applications iPhone?

J'ai une application iPhone prête et approuvée par l'App Store. Maintenant, je veux créer différents thèmes pour mon application. Quelqu'un peut-il m'aider, avec des informations/liens/étapes sur la façon de créer des thèmes pour mon application?

Je veux créer un thème Métal pour les garçons et un thème Rose pour les filles. Encore une fois par thème, je veux dire, l'application entière (caractéristiques et fonctionnalités) va rester la même, mais selon qui est l'utilisateur (garçon ou fille), il/elle peut choisir le thème qu'il souhaite voir. Et lorsque le thème change, seules les images/fond/musique changeront en fonction du thème appliqué.

Merci beaucoup!

55
pat

C'est assez difficile car les applications n'ont pas l'équivalent d'une feuille de style CSS.

Vous devez d'abord déterminer les parties de l'application que vous souhaitez habiller et quand vous souhaitez autoriser l'utilisateur à échanger des skins.

Je vais supposer que vous voulez changer les images et les couleurs de police, et que ça va si l'utilisateur doit relancer l'application pour changer la peau (cela rendra les choses plus simples pour l'instant).

Créez une liste contenant toutes vos images et couleurs skinnable. La liste sera un dictionnaire avec des noms de touches neutres et sensibles au thème pour les images et les couleurs (par exemple, vous n'avez pas de couleur appelée "rouge", appelez-la "primaryHeadingColor"). Les images seront des noms de fichiers et les couleurs peuvent être des chaînes hexadécimales, par ex. FF0000 pour le rouge.

Vous aurez un plist pour chaque thème.

Créez une nouvelle classe appelée ThemeManager et faites-en un singleton en ajoutant la méthode suivante:

+ (ThemeManager *)sharedManager
{
    static ThemeManager *sharedManager = nil;
    if (sharedManager == nil)
    {
        sharedManager = [[ThemeManager alloc] init];
    }
    return sharedManager;
}

La classe ThemeManager aura une propriété NSDictionary appelée "styles", et dans la méthode init vous chargerez le thème dans votre dictionnaire de styles comme ceci:

- (id)init
{
    if ((self = [super init]))
    {
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        NSString *themeName = [defaults objectForKey:@"theme"] ?: @"default";

        NSString *path = [[NSBundle mainBundle] pathForResource:themeName ofType:@"plist"];
        self.styles = [NSDictionary dictionaryWithContentsOfFile:path];
    }
    return self;
}

(Remarque: certaines personnes n'aiment pas faire beaucoup de travail dans une méthode init. Je n'ai jamais trouvé que c'était un problème, mais si vous préférez, créez une méthode distincte pour charger le dictionnaire de thèmes et l'appeler à partir de votre application. code de configuration).

Remarquez comment je tire le nom de la liste de thèmes des valeurs par défaut de l'utilisateur. Cela signifie que l'utilisateur peut sélectionner un thème dans vos préférences et l'enregistrer et l'application chargera ce thème la prochaine fois qu'il sera lancé. J'ai mis un nom de thème par défaut "default" si aucun thème n'est sélectionné, alors assurez-vous d'avoir un fichier de thème default.plist (ou changez le @ "default" dans le code en quel que soit le nom de votre liste de thèmes par défaut) ).

Maintenant que vous avez chargé votre thème, vous devez l'utiliser; Je suppose que votre application comporte différentes images et étiquettes de texte. Si vous chargez et disposez ces éléments dans le code, cette partie est facile. Si vous le faites dans des plumes, c'est un peu plus compliqué, mais je vous expliquerai comment gérer cela plus tard.

Maintenant, normalement, vous chargez une image en disant:

UIImage *image = [UIImage imageNamed:@"myImage.png"];

Mais si vous voulez que cette image soit à thème, vous devrez maintenant la charger en disant

NSDictionary *styles = [ThemeManager sharedManager].styles;
NSString *imageName = [styles objectForKey:@"myImageKey"];
UIImage *image = [UIImage imageNamed:imageName];

Cela cherchera dans votre fichier de thème l'image à thème qui correspond à la clé "myImageKey" et la chargera. Selon le fichier de thème que vous avez chargé, vous obtiendrez un style différent.

Vous utiliserez beaucoup ces trois lignes, vous voudrez peut-être les envelopper dans une fonction. Une bonne idée serait de créer une catégorie sur UIImage qui déclare une méthode appelée quelque chose comme:

+ (UIImage *)themeImageNamed:(NSString *)key;

Ensuite, pour l'utiliser, vous pouvez simplement remplacer tous les appels à [UIImage imageNamed: @ "foo.png"]; avec [UIImage themeImageNamed: @ "foo"]; où foo est maintenant la clé du thème au lieu du nom réel de l'image.

D'accord, c'est tout pour thématiser vos images. Pour donner un thème aux couleurs de vos étiquettes, supposons que vous définissez actuellement les couleurs de vos étiquettes en disant:

 someLabel.color = [UIColor redColor];

Vous devez maintenant remplacer cela par:

NSDictionary *styles = [ThemeManager sharedManager].styles;
NSString *labelColor = [styles objectForKey:@"myLabelColor"];
someLabel.color = [UIColor colorWithHexString:labelColor];

Maintenant, vous avez peut-être remarqué que UIColor n'a pas de méthode "colorWithHexString:" - vous devrez l'ajouter en utilisant une catégorie. Vous pouvez rechercher des solutions "UIColor with hex string" sur Google pour trouver le code pour le faire, ou j'ai écrit une catégorie pratique qui fait cela et un peu plus ici: https://github.com/nicklockwood/ColorUtils

Si vous avez fait attention, vous penserez également qu'au lieu d'écrire ces trois lignes encore et encore, pourquoi ne pas ajouter une méthode à UIColor appelée:

+ (UIColor *)themeColorNamed:(NSString *)key;

Tout comme nous l'avons fait avec UIImage? Bonne idée!

Donc c'est tout. Vous pouvez désormais créer un thème pour n'importe quelle image ou étiquette dans votre application. Vous pouvez utiliser la même astuce pour définir le nom de la police ou un certain nombre d'autres propriétés visuelles potentiellement thématiques.

Il y a juste une toute petite chose que nous avons oubliée ...

Si vous avez construit la plupart de vos vues sous forme de plumes (et je ne vois aucune raison pour laquelle vous ne le feriez pas), ces techniques ne fonctionneront pas car vos noms d'image et les couleurs de police sont enfouis dans des données de plumes impénétrables et ne sont pas définir dans votre code source.

Il existe quelques approches pour résoudre ce problème:

1) Vous pouvez créer des copies thématiques de vos plumes en double, puis mettre les noms des plumes dans votre liste de thèmes et les charger depuis votre gestionnaire de thèmes. Ce n'est pas trop mal, implémentez simplement la méthode nibName de vos contrôleurs de vue comme ceci:

- (NSString *)nibName
{
    NSDictionary *styles = [ThemeManager sharedManager].styles;
    return [styles objectForKey:NSStringFromClass([self class])];
}

Remarquez mon astuce d'utiliser le nom de classe du contrôleur de vue comme clé - cela vous évitera de taper car vous pouvez simplement créer un ThemeViewController de base avec cette méthode et faire en sorte que tous vos contrôleurs de vue thématiques en héritent.

Cette approche implique cependant de conserver plusieurs copies de chaque plume, ce qui est un cauchemar de maintenance si vous devez changer les écrans plus tard.

2) Vous pouvez créer des IBOutlets pour toutes les imagesViews et étiquettes de vos plumes, puis définir leurs images et leurs couleurs dans le code dans votre méthode viewDidLoad. C'est probablement l'approche la plus lourde, mais au moins vous n'avez pas de nibs en double à maintenir (c'est essentiellement le même problème que la localisation des nibs btw, et à peu près les mêmes options de solution).

3) Vous pouvez créer une sous-classe personnalisée de UILabel appelée ThemeLabel qui définit automatiquement la couleur de la police en utilisant le code ci-dessus lorsque l'étiquette est instanciée, puis utilisez ces ThemeLabels dans vos fichiers nib au lieu d'UILabels standard en définissant la classe de l'étiquette sur ThemeLabel dans Générateur d'interface. Malheureusement, si vous avez plusieurs polices ou couleurs de police, vous devrez créer une sous-classe UILabel différente pour chaque style différent.

Ou vous pourriez être sournois et utiliser quelque chose comme la balise de vue ou la propriété accessibilityLabel comme clé de dictionnaire de style afin que vous puissiez avoir une seule classe ThemeLabel et définir l'étiquette d'accessibilité dans Interface Builder pour sélectionner le style.

La même astuce pourrait fonctionner pour ImageViews - créer une sous-classe UIImageView appelée ThemeImageView qui, dans la méthode awakeFromNib, remplace l'image par une image de thème basée sur la propriété tag ou accessibilityLabel.

Personnellement, j'aime mieux l'option 3 car elle économise le codage. Un autre avantage de l'option 3 est que si vous souhaitez pouvoir échanger des thèmes au moment de l'exécution, vous pouvez implémenter un mécanisme dans lequel votre gestionnaire de thèmes recharge le dictionnaire de thèmes, puis diffuse une NSNotification à tous les ThemeLabels et ThemeImageViews leur disant de se redessiner. Cela ne prendrait probablement que 15 lignes de code supplémentaires.

Quoi qu'il en soit, vous avez là une solution complète de thème d'application iOS. Vous êtes les bienvenus!

MISE À JOUR:

Depuis iOS 5, il est désormais possible de définir des attributs personnalisés par keyPath dans Interface Builder, ce qui signifie qu'il n'est plus nécessaire de créer une sous-classe de vue pour chaque propriété thématique, ou d'abuser de la balise ou de l'accessibilitéLabel pour sélectionner des styles. Donnez simplement à votre sous-classe UILabel ou UIImageView une propriété de chaîne pour indiquer la clé de thème à utiliser dans le plist, puis définissez cette valeur dans IB.

MISE À JOUR 2:

Depuis iOS 6, il existe maintenant un système de skinning intégré à iOS qui vous permet d'utiliser une propriété appelée proxy UIAppearance pour skinner toutes les instances d'un donné la classe de contrôle à la fois (il y a un bon tutoriel sur les API UIAppearance ici ). Cela vaut la peine de vérifier si cela suffit pour vos besoins de skinning, mais sinon, la solution que j'ai décrite ci-dessus fonctionne toujours bien et peut être utilisée à la place, ou en combinaison avec UIAppearance.

130
Nick Lockwood