这很困难,因为应用没有 CSS 样式表。
首先,您需要确定要为应用的哪些部分设置皮肤,以及何时允许用户更换皮肤。
我将假设您想要更改图像和字体颜色,并且如果用户必须重新启动应用程序来更改皮肤也没关系(这将使事情现在变得更简单)。
创建一个包含所有可换肤图像和颜色的 plist。 plist 将是一个字典,其中包含图像和颜色的合理的主题中性键名称(例如,没有称为“红色”的颜色,将其称为“primaryHeadingColor”)。图像将是文件名,颜色可以是十六进制字符串,例如FF0000 为红色。
每个主题都有一个 plist。
创建一个名为 ThemeManager 的新类,并通过添加以下方法使其成为单例:
+ (ThemeManager *)sharedManager
{
static ThemeManager *sharedManager = nil;
if (sharedManager == nil)
{
sharedManager = [[ThemeManager alloc] init];
}
return sharedManager;
}
ThemeManager 类将有一个名为“styles”的 NSDictionary 属性,在 init 方法中,您将像这样将主题加载到您的样式字典中:
- (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;
}
(注意:有些人不喜欢在 init 方法中做很多工作。我从来没有发现它是一个问题,但如果你愿意,可以创建一个单独的方法来加载主题字典并调用它来自您应用的设置代码)。
请注意我是如何从用户默认值中获取主题 plist 的名称的。这意味着用户可以在您的首选项中选择一个主题并保存它,该应用程序将在下次启动时加载该主题。如果没有选择主题,我会输入默认主题名称“default”,因此请确保您有一个 default.plist 主题文件(或将代码中的 @"default" 更改为实际调用的默认主题 plist )。
现在您已经加载了您需要使用的主题;我假设您的应用程序有各种图像和文本标签。如果您在代码中加载和布置这些内容,那么这部分很容易。如果你是用笔尖做的,那就有点棘手了,但我稍后会解释如何处理。
现在通常您会通过以下方式加载图像:
UIImage *image = [UIImage imageNamed:@"myImage.png"];
但是,如果您希望该图像具有主题性,您现在需要通过说来加载它
NSDictionary *styles = [ThemeManager sharedManager].styles;
NSString *imageName = [styles objectForKey:@"myImageKey"];
UIImage *image = [UIImage imageNamed:imageName];
这将在您的主题文件中查找与键“myImageKey”匹配的主题图像并加载它。根据您加载的主题文件,您将获得不同的样式。
您将经常使用这三行代码,因此您可能希望将它们封装在一个函数中。一个好主意是在 UIImage 上创建一个类别,该类别声明一个方法,类似于:
+ (UIImage *)themeImageNamed:(NSString *)key;
然后要使用它,您只需替换对 [UIImage imageNamed:@"foo.png"]; 的任何调用即可。与 [UIImage themeImageNamed:@"foo"];其中 foo 现在是主题键而不是实际的图像名称。
好的,这就是您的图像主题。要为标签颜色设置主题,假设您当前正在通过以下方式设置标签颜色:
someLabel.color = [UIColor redColor];
您现在将其替换为:
NSDictionary *styles = [ThemeManager sharedManager].styles;
NSString *labelColor = [styles objectForKey:@"myLabelColor"];
someLabel.color = [UIColor colorWithHexString:labelColor];
现在您可能已经注意到 UIColor 没有方法“colorWithHexString:” - 您必须使用类别添加它。您可以在 Google 上搜索“带有十六进制字符串的 UIColor”解决方案来找到执行此操作的代码,或者我已经编写了一个方便的类别来执行此操作以及更多内容:https://github.com/nicklockwood/ColorUtils
如果你一直在关注,你也会想,与其一遍又一遍地写这三行代码,不如给 UIColor 添加一个名为的方法:
+ (UIColor *)themeColorNamed:(NSString *)key;
就像我们对 UIImage 所做的那样?好主意!
就是这样。现在,您可以为应用中的任何图像或标签设置主题。您可以使用相同的技巧来设置字体名称,或任何数量的其他潜在的可主题化的视觉属性。
我们忘记了一件小事......
如果您已将大部分视图构建为 nib(我认为没有理由不这样做),那么这些技术将不起作用,因为您的图像名称和字体颜色隐藏在难以穿透的 nib 数据中,并且'未在您的源代码中设置。
有几种方法可以解决这个问题:
1) 您可以复制您的笔尖的主题副本,然后将笔尖名称放入您的主题 plist 并从您的主题管理器加载它们。这还不错,只需像这样实现视图控制器的 nibName 方法:
- (NSString *)nibName
{
NSDictionary *styles = [ThemeManager sharedManager].styles;
return [styles objectForKey:NSStringFromClass([self class])];
}
请注意我使用视图控制器的类名作为键的巧妙技巧 - 这将为您节省一些输入,因为您可以使用该方法创建一个基本的 ThemeViewController 并让所有可主题化的视图控制器都继承自它。
这种方法确实意味着维护每个笔尖的多个副本,如果您以后需要更改任何屏幕,这是维护的噩梦。
2) 您可以为您的笔尖中的所有图像视图和标签制作 IBOutlets,然后在您的 viewDidLoad 方法的代码中设置它们的图像和颜色。这可能是最麻烦的方法,但至少您不需要维护重复的 nib(这与本地化 nibs 本质上是相同的问题,并且几乎相同的解决方案选项)。
3) 您可以创建一个名为 ThemeLabel 的自定义 UILabel 子类,当标签被实例化时,它会使用上面的代码自动设置字体颜色,然后通过设置标签的类在您的 nib 文件中使用这些 ThemeLabels 而不是常规的 UILabel到 Interface Builder 中的 ThemeLabel。不幸的是,如果您有多个字体或字体颜色,则需要为每种不同的样式创建不同的 UILabel 子类。
或者你可能会变得狡猾,使用视图标签或accessibilityLabel 属性作为样式字典键,这样你就可以拥有一个ThemeLabel 类并在Interface Builder 中设置可访问性标签来选择样式。
同样的技巧也适用于 ImageViews - 创建一个名为 ThemeImageView 的 UIImageView 子类,在 awakeFromNib 方法中,该子类根据标签或accessibilityLabel 属性将图像替换为主题图像。
我个人最喜欢选项 3,因为它节省了编码。选项 3 的另一个优点是,如果您希望能够在运行时交换主题,您可以实现一种机制,您的主题管理器重新加载主题字典,然后向所有 ThemeLabels 和 ThemeImageView 广播一个 NSNotification,告诉他们重绘自己。这可能只需要额外的 15 行代码。
无论如何,您已经有了一个完整的 iOS 应用主题解决方案。不客气!
更新:
从 iOS 5 开始,现在可以在 Interface Builder 中通过 keyPath 设置自定义属性,这意味着不再需要为每个主题属性创建视图子类,或滥用标签或 accessLabel 来选择样式。只需给你的 UILabel 或 UIImageView 子类一个字符串属性来指示它应该从 plist 中使用哪个主题键,然后在 IB 中设置该值。
更新 2:
从 iOS 6 开始,现在 iOS 内置了一个有限的换肤系统,允许您使用名为 UIAppearance 代理 的属性一次性为给定控件类的所有实例换肤(有关于 UIAppearance API 的好教程 here)。值得检查这是否足以满足您的换肤需求,但如果不是,我上面概述的解决方案仍然有效,可以替代使用,或与 UIAppearance 结合使用。