iOS应用中的动态主题

鲜于凯康
2023-12-01

介绍 (Introduction)

About a year ago the iOS 13 was announced at WWDC19 and one of its most anticipated features was the Dark Mode. With the introduction of this new feature a bunch of developers started to think about what was the best way to implement it making themes implementation straightforward. I was one of these developers. In this article I’ll share some of my experiences implementing dynamic themes, while also sharing my thoughts on what I think it works better for making this implementation easier and more generic.

大约一年前,iOS 13在WWDC19上发布,其最令人期待的功能之一就是Dark Mode。 随着这一新功能的引入,许多开发人员开始考虑什么是实现它的最佳方法,从而使主题的实现变得简单。 我是这些开发人员之一。 在本文中,我将分享一些实现动态主题的经验,同时还将分享我对使实现更容易和更通用的方法更好的想法。

NOTE: For more details about the implementation metionationed in this article take a look at the following repo:

注意:有关本文提及的实现的更多详细信息,请查看以下存储库:

“为什么这么黑?” (“Why so Dark?”)

Let’s track this from the beginning. The first conclusion I came when developing my first app that would be using dark mode is: “Why only dark mode? Why not providing the hability of the application to have not only the dark theme but any number of themes I want?”. Well, certainly that’s would be the best option for sure, but how to provide a technical solution that is generic enough for that? In the WWDC19 Apple was only talking about implementing the binary themes (light/dark). Actualy, everything was set by Apple so things works this way, the new way you have now to write the UIColor, describing the light implentation, and the dark theme implementation. Last but not least, the custom solution provided by Apple works only on iOS 13+, so that could be a problem as well. In resume, the Apple solution’s works perfeclty if you want to implement binary themes, but if you want some more dynamic you have to craft something more robust.

让我们从头开始进行跟踪。 在开发使用暗模式的第一个应用程序时,我得出的第一个结论是:“为什么只有暗模式? 为什么不提供应用程序的功能,使其不仅具有深色主题,而且还具有我想要的任意多个主题?”。 好吧,当然这肯定是最好的选择,但是如何提供足够通用的技术解决方案呢? 在WWDC19中,Apple只在谈论实现二进制主题(浅色/深色)。 实际上,一切都是由Apple设置的,因此一切都可以通过这种方式来进行,这是您现在编写UIColor的新方式,描述了浅色的实现和深色主题的实现。 最后但并非最不重要的一点是,Apple提供的自定义解决方案仅在iOS 13+上有效,因此也可能是一个问题。 在简历中,如果您想实现二进制主题,则Apple解决方案可以正常工作,但是如果您想要更多的动态性,则必须设计出更强大的功能。

提供背景的颜色 (Colours that provides context)

The first thing we have to decide before starting defining our code structure is how we gonna call our colours. In a Theme, a Colour is basically like an atom of our system. We can have dozens or even hundreads of colors in our system and the first problem I came through in mind was: “how to name all those colours? and how to keep coeherence in that naming system?”. Before having multiple themes I had colours with names like moneyGreen, infoBlue or alertRed but from now on those names started having no sense at all, I cannot call a colour somethingGreen because in a theme it can be green but for other theme it cannot be true.

在开始定义代码结构之前,我们必须决定的第一件事是如何命名颜色。 在主题中,颜色基本上就像我们系统的原子。 我们的系统中可以有数十种甚至数百种颜色,而我想到的第一个问题是:“如何命名所有这些颜色? 以及如何在该命名系统中保持一致性?”。 在具有多个主题之前,我使用了诸如moneyGreeninfoBluealertRed之类的颜色 ,但是从现在开始,这些名称开始变得毫无意义,我无法将其命名为somethingGreen,因为在一个主题中它可以是绿色,但对于其他主题,它就不可能是真实的。 。

So, from now on I had to start renaming my colours using context and not colours itself on its names. I mean, instead of moneyGreen I can now call this color only money. That represents the context of this colour on my system and that doesn’t change through the themes, so that could be a good name, right ?

因此,从现在开始,我必须开始使用上下文重命名我的颜色,而不是为其名称本身着色。 我的意思是,我现在可以将这种颜色称为money ,而不是moneyGreen 。 那代表了我系统上这种颜色的上下文,并且在整个主题中都没有改变,所以这可能是个好名字,对吗?

Hm….. No!

嗯.....不!

Because, like I said I can have dozens or hundread of those colours on an app. I don’t want to be wasting time thinking on this for every colour I’m going to name. And also, I can have two colours for money, how can I name it? money1 and money2? But what’s money1 and what’s the difference from money2, it’s not very expressive, you see the problem?

因为,就像我说的那样,我可以在一个应用程序中拥有数十种颜色。 我不想浪费时间思考我要命名的每种颜色。 而且,我可以用两种颜色赚钱,如何命名? money1money2 ? 但是,什么是MONEY1的,什么是从money2的区别,它不是非常传神,你看这个问题?

I started then to think in a better way to name my colours, a way that make all my colour names very expressive and that makes that I don’t have to think a lot to define it’s name.

然后,我开始思考以一种更好的方式来命名我的颜色,这种方式使我所有的颜色名称都具有很强的表现力,而不必为定义其名称而花太多时间。

For my luck, there is already a colour name system that met all these requirements: The Material Design Color System.

幸运的是,已经有一个满足所有这些要求的颜色名称系统: 物料设计颜色系统。

And I know what you may be thinking right now “Hey, this guy is using Google Stuff, he’s probably an undercover enemy, closing this article right now!!111!”. Well, I promisse I’m not. Thing is, the color system used by google on Material Design is very simple and lean, with this we can have only about 20 colors for our entire application, without never having to worry in define a colour name again in life, because it covers every case we will have in our implementation. Are you not convinced? I will prove it to you!

而且我知道您现在可能在想什么:“嘿,这个人正在使用Google Stuff,他可能是一个秘密的敌人,请立即关闭本文!111!”。 好吧,我保证我不是。 事实是,谷歌在Material Design上使用的颜色系统非常简单和精益,因此我们整个应用程序只能使用约20种颜色,而不必担心在生活中再次定义颜色名称,因为它涵盖了每一个我们的实施中会遇到的情况。 你不相信吗? 我会向你证明!

创建具有继承的动态主题 (Creating Dynamic Themes with Inheritance)

public enum ColorName: String {
case colorPrimary
case colorSecondary
case colorSurface
case colorOnSurface
case colorOnSecondary
case textColorPrimary
case textColorSecondary
case textColorSecondaryVariant
case colorError
case colorOnError
case rippleOnSecondaryColor
case separatorColor
case infoColorVariant
case colorStatusBar
case colorOnPrimary
case navigationBar
case borderColor
case clear
}

Above are all the the colours I recommend you to have in your application. With only these colours you can cover every case you’ll have in your entire app, in every kind of app you are developing. The first thing you’ll notice is, only the colour names are defined in this enum, so the colours are being defined in anywhere else (we will talk about this soon). Another thing is, the last threee colours are not from Material Color System, they are included here just for convinience.

以上是我建议您在应用程序中使用的所有颜色。 仅使用这些颜色,您就可以涵盖整个应用程序,正在开发的每种应用程序中遇到的每种情况。 您会注意到的第一件事是,在此枚举中仅定义了颜色名称,因此在其他任何地方都定义了颜色(我们将在稍后讨论)。 另一件事是,最后三种颜色不是来自Material Color System,为了方便起见,将它们包括在此处。

To define our themes in the most generic way possible the best option would be: setting it outside the code! In a well difunded file notation like XML, YAML, PLIST or JSON. This way you are free to fetch it from the server side if you want or even to share it with someone outside of the tech team as well. In my case I choosed to set it in a JSON file called Themes.json. This is the file where I define which theme I’m going to use in the system and every colour it uses.

要以最通用的方式定义主题,最好的选择是:在代码外设置主题! 格式明确的文件表示法,例如XML,YAML,PLIST或JSON。 这样,您可以自由地从服务器端获取它,甚至可以与技术团队以外的人共享它。 就我而言,我选择将其设置在一个名为Themes.json的JSON文件中。 这是我定义要在系统中使用的主题及其使用的每种颜色的文件。

For the AppThemeSample linked at the begining of this article the Themes.json files seems like this:

对于本文开头链接的AppThemeSample,Themes.json文件看起来像这样:

As you may have already noticed, I don’t only have the light and the dark theme in this Themes file, I also have a Bulbassaur, Pikachu, Charmander and Squirtle themes, each one with its own colour definitions.Also, it’s not every theme that has every colour definition on it scope, some have only one or two colours, and that’s the way we are able to have dynamic themes in a easy and fast way: we use inheritance in our themes and a recursive call for colour properties. Take a look:

正如您可能已经注意到的那样,在此主题文件中我不仅拥有浅色和深色主题,还具有BulbassaurPikachuCharmanderSquirtle主题,每个主题都有其自己的颜色定义。主题具有范围内的每种颜色定义,有些主题只有一种或两种颜色,这就是我们能够以一种便捷的方式拥有动态主题的方式:我们在主题中使用继承,并递归调用颜色属性。 看一看:

A theme can always have a parent theme, that can be set on the Themes.json. If a colour, for example colorPrimary, is being called for ThemeA and it doens’t have it colour defined but its parent has, the colorPrimary returned will be the ThemeA’s parent. If the parent doens’t have the color the algorithm will search for the parent’s parent and so on. This way we make that, with only the 18 colors defined on our ColorName enum we can set any color through the entire system, without having to create new colours ever again. Because, think, if you have, for example, a cell that has a different background colour than your controller, it makes more sense to have a theme for your cell that inherit from your controller (or for another theme that makes more sense) and then you override only the background color for the cell theme instead of creating a new color name. Futhermore, you define a computed variable on the ColourName enum that will return the correct colour based on the theme you want, what makes the solution even more generic:

主题始终可以有一个父主题,可以在Themes.json上进行设置。 如果正在为ThemeA调用一种颜色(例如colorPrimary),并且没有定义颜色,但是其父级定义了颜色,则返回的colorPrimary将是ThemeA的父级。 如果父母没有颜色,算法将搜索父母的父母,依此类推。 这样,我们只需在ColorName枚举上定义18种颜色,就可以在整个系统中设置任何颜色,而无需再次创建新颜色。 因为,例如,如果您拥有一个与控制器具有不同背景颜色的单元格,那么为您的单元格创建一个从控制器继承的主题(或另一个更有意义的主题)更有意义。那么您将仅覆盖单元格主题的背景颜色,而不创建新的颜色名称。 此外,您在ColourName枚举上定义了一个计算变量,该变量将根据所需主题返回正确的颜色,这使解决方案更加通用:

Cool, right? :)

酷吧? :)

然后去哪儿? (Where to go from here?)

The repository linked at the beginning of this article provides a complete example of how I implemented the complete idea described here, you can take I look on the the AppTheme folder for the framework implementation details or in the AppThemeSample for a example of application using this idea to implement dynamic themes. The AppTheme also implements dynamic buttons and labels definitions using JSON files (a similar idea from what was discussed here), so feel free to explore the code. The idea is to convert the AppTheme into a cocoapod library, the only prequerisite missing for that is make that the Themes enum can be set outside the AppTheme. I opened the code in that repository so you are free to add your collaboration if you want. You can also, simply drag and drop the code there to your code and that will perfectly work as a library.

本文开头链接的存储库提供了有关如何实现此处描述的完整概念的完整示例,您可以在AppTheme文件夹中查看框架实现详细信息,或者在AppThemeSample中查看使用此概念的应用程序示例实施动态主题。 AppTheme还使用JSON文件实现动态按钮和标签定义(与此处讨论的想法类似),因此可以随时探索代码。 想法是将AppTheme转换为cocoapod库,唯一缺少的前古怪对象是可以将Themes枚举设置在AppTheme之外。 我在该存储库中打开了代码,因此您可以根据需要随意添加协作。 您也可以简单地将代码拖放到您的代码中,这完全可以用作库。

So, that’s it! With this we’re done. This was the first time I was written a technical article so I hope that you have enjoied reading and that may this content have been useful in some way to you. Feel free to leave a comment if you have some doubt or any feedback :)

就是这样了! 这样就完成了。 这是我第一次写技术文章,所以希望您喜欢阅读,并希望此内容对您有所帮助。 如果您有任何疑问或任何反馈意见,请随时发表评论:)

Happy Coding!

编码愉快!

翻译自: https://medium.com/swlh/dynamic-themes-in-ios-apps-eec69965c0ef

 类似资料: