如何为 iPhone 应用程序创建多个主题/皮肤? [关闭]

Posted

技术标签:

【中文标题】如何为 iPhone 应用程序创建多个主题/皮肤? [关闭]【英文标题】:How to create Multiple Themes/Skins for iphone apps? [closed] 【发布时间】:2012-02-13 17:10:11 【问题描述】:

我有一个 iPhone 应用程序已准备好并已获得应用商店的批准。现在我想为我的应用创建不同的主题。有人可以帮助我,提供有关如何为我的应用创建主题的信息/链接/步骤吗?

我想为男孩创建一个金属主题,为女孩创建一个粉色主题。再次通过主题我的意思是,整个应用程序(特性和功能)将保持不变,但取决于用户是谁(男孩或女孩),他/她可以选择他们希望看到的主题。并且当主题改变时,只有图像/背景/音乐会根据应用的主题而改变。

非常感谢!

【问题讨论】:

主题到底是什么意思?你想要什么“主题”? 我很确定他指的是 Windows 主题中的主题! 这个问题不应该被关闭,这是一个完全合法的问题,具有一组有趣的可能技术解决方案,并且大多数人都理解“主题”在应用程序上下文中的含义。跨度> 对不起,如果我不能更清楚,但我想问的是:我想为男孩创建一个金属主题,为女孩创建一个粉红色主题。再次通过主题我的意思是,整个应用程序(特性和功能)将保持不变,但取决于用户是谁(男孩或女孩),他/她可以选择他们希望看到的主题。并且当主题改变时,只有图像/背景/音乐会根据应用的主题而改变。谢谢! 谢谢,我已经更新了问题。我认为它会理解我所说的“主题”的意思。我不知道这么多人很难理解我的意思。非常感谢! 【参考方案1】:

这很困难,因为应用没有 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 结合使用。

【讨论】:

这真是太棒了,谢谢尼克 天哪!我认为这是我读过的最好的、最好的、最好的指南/答案/帮助/教程。还没有结束,但是你会一直惊讶于你是如何花时间用简单的方式很好地解释事情的。真的很棒!我希望 IRC 频道里的人也这么好。非常感谢。 @Luke,这不是苹果官方的做法。 iOS5 中提供了一些新的主题选项,例如使用 UIAppearance 代理为您的视图设置全局样式。但我建议的技术在 iOS5 和 6 中仍然非常有效,并且仍然是在运行时在主题之间交换的最简单方法。 为什么这样的问题会在 SO 上关闭?完全没有意义。这是网站的失败。感谢@NickLockwood 的帖子! @ShumaisUlHaq 是的,自从我已经提到的 iOS 5 中添加了用户可定义的运行时属性以来,这方面没有任何重大变化。现在有更多控件支持 tintColor 等,这很好,而且还有 UIAppearance 代理,它允许您基于每个类设置样式,而不必单独配置每个控件,但这种方法仍然是唯一正确的恕我直言,灵活的方式在您的应用中为任何内容设置皮肤。

以上是关于如何为 iPhone 应用程序创建多个主题/皮肤? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何为 QtCreator 创建自定义主题

如何为 iPhone 创建自定义 Interface Builder 插件?

如何为我的 iPhone 应用程序创建示例 SQLite DB?

如何为我的 Flash 页面创建 iPhone 应用程序? [关闭]

如何为 iPhone Light 版本应用程序创建 xcode 项目?

如何为 iPhone 6 / 6 Plus 仅横向应用程序创建启动图像?