iOS 主题/皮肤之 SakuraKit

sakura-kit-logo

前言

目前市场上很多 App 都有主题变更、皮肤切换的功能。随着项目代码量的不断增长,业务不断完善,功能性代码逐渐趋于模块化,尤其是在多人协作开发同一个项目时,模块解耦尤为重要,同时,公共基础库的功能性代码使用越简单越好。

前段时间在维护旧项目时,收到 App 主题变更、皮肤切换的需求,其包括 App 中各种图标、色值、文字、字体等都包括在内,都需实现主题化。主要用于:

  1. 活动主题展示:比较典型的是类似京东618、天猫淘宝购物节主题变更。
  2. 用户夜间模式:类似阅读相关 App 的夜间模式,如:简书等。
  3. 用户主题变更:用户可通过本地或者远程下载喜欢的主题,如:网易云音乐、QQ 音乐等 App 主题变更。

由于老项目代码比较混乱,功能模块耦合严重以及开发时间等综合因素,在实现 App 主题变更、皮肤切换的功能的同时,想要在尽量不修改旧代码的基础上增加新的功能是比较麻烦的。

由于没有合适的第三方库,于是自己手撸了一个库 SakuraKit,并开源,希望能帮到需要的朋友。

下面我们开始介绍 SakuraKit 及快速入门。

SakuraKit

SakuraKit,是一个轻量级的、专门用于 App 主题变更、皮肤切换的开源库(灵感源自 SwiftTheme、DKNightVersion等),采用函数式 + 链式的编码方式,简单实用、方便理解、利于维护。

快速入门

效果

在体验前,我们先来看看效果图:

sakura-kit-demo

体验

下面以 UIButton 为例,介绍如何使用 SakuraKit 进行主题化:


UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    
button.sakura
.backgroundColor(@"Home.buttonBackgroundColor")
.titleColor(@"Home.buttonTitleColor", UIControlStateNormal);

上述代码是给一个 button 的背景色(backgroundColor)以及标题颜色(titleColor)进行主题化。其中 Home.buttonBackgroundColorHome.buttonTitleColor 属配置文件中的 KeyPath,配置文件的功能有点类似语言本地化文件(Localizable.strings)。后文会重点介绍如何设置配置文件。

到此为止,我们已经实现了 button 按钮主题化功能,如果你想切换主题,可以调用如下 API:


+ (BOOL)shiftSakuraWithName:(TXSakuraName *)name type:(TXSakuraType)type;

其中 name 参数代表主题的名称,type 参数代表主题类型(目前有两种:沙盒本地)。

现在我们再具体的介绍一下如何使用 SakuraKit

配置文件

做过 App 语言本地化的童鞋,应该比较熟悉 Localizable.strings 文件配置,同理,我们在使用 SakuraKit 对 App 进行主题化时,也需要进行类似的配置。目前支持 .json.plist 两种文件格式。

下面我们以 .json 文件格式做示例:

{
    "Home":{
            "buttonBackgroundColor":"#BB503D",
            "buttonTitleColor":"#4AF2A1"
        }
}

在上述体验代码中,我们看到这样的字符串:Home.buttonBackgroundColorHome.buttonTitleColor,这其实就是配置文件中字典的 KeyPath,通过 KeyPath 可以取得不同主题下的值,如:色值、图片名称、文字、字体大小等等。

注意事项:

  1. 每个主题都有自己配置文件,包括本地和沙盒主题。(本地主题名叫 default)。
  2. 主题名称与配置文件名称一致,如:某个主题名叫 fish,那么该主题相应的配置文件就应命名为fish.json。(建议遵守该约定
  3. 不同本地主题的切图命名要做区分,不同远程主题的切图命名应一致

本地主题

本地主题,即用户无需下载的主题,在 App Bundle 中。除了 App 本身自带的默认主题外,SakuraKit 还能够为 App 新增多种本地主题。

配置步骤如下:

步骤一

新建 .json 配置文件,比如新建一个名叫 typewriter 的主题,因此配置文件命名为 typewriter.json。

步骤二

配置一套切图,并且命名与已有的主题要做区分。

步骤三

完成上述步骤后,在 AppDelegate 中 -application:application didFinishLaunchingWithOptions:launchOptions API 注册所有本地主题:


// 注意:本地默认主题无需注册
[TXSakuraManager registerLocalSakuraWithNames:@[@"typewriter"]];

步骤四

调用切换主题 API 即可切换至该指定主题:


[TXSakuraManager shiftSakuraWithName:@"typewriter" type:TXSakuraTypeMainBundle];
    

远程主题

远程主题(资源压缩包.zip),即用户通过网络下载的主题,后台可动态配置。同本地主题一致,分为两部分:配置文件 + 切图。当配置文件和切图都弄好后,将文件夹打包成zip文件,传给后台即可。主题数据格式如下(仅供参考):


{
    "name": "嘻多猴",
    "sakuraName": "monkey",
    "url": "http:\\image.tingxins.cn\sakura\monkey.zip"
}

sakuraName 是切换主题时用的名称,而 url 是该主题的下载地址。(注:如果 sakuraName 字段传空,那么主题的名称将默认为下载的压缩包名称

当远程主题下载完毕后,可以这样切换主题:


[TXSakuraManager shiftSakuraWithName:sakuraName type:TXSakuraTypeSandBox];

值得一提的是,SakuraKit 提供了一些主题下载的简单接口,支持多种主题同时下载等操作,并且支持 Block 和 Delegate 两种方式的回调,同时用户还可自定义下载操作。

下面我们来依次介绍一下主题下载。

Block 方式

我们直接来介绍 API :


[[TXSakuraManager manager] tx_sakuraDownloadWithInfos:sakuraModel downloadProgressHandler:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
    // 下载进度回调
} downloadErrorHandler:^(NSError * _Nullable error) {
    // 下载过程出现错误回调
} unzipProgressHandler:^(unsigned long long loaded, unsigned long long total) {
    // 主题下载完成后,解压进度回调
} completedHandler:^(id<TXSakuraDownloadProtocol> _Nullable infos, NSURL * _Nullable location) {
    // 主题包解压完毕回调
} ];


其中 sakuraModel 模型数据遵守了 TXSakuraDownloadProtocol 协议,具体使用详见 SakuraDemo_OC,在 DownloadSakuraController 控制器演示了该操作。

Delegate 方式

步骤一

直接调用 API 实现主题下载:


[[TXSakuraManager manager] tx_sakuraDownloadWithInfos:sakuraModel delegate:self];


步骤二

如果针对步骤一的下载操作需要回调,那么可以选择性的再实现以下方法:


// 重复点击下载某一主题,如果该主题已经处于下载中或者本地存在时将会回调,其中 status 标识该 downloadTask 状态。
- (void)sakuraManagerDownload:(TXSakuraManager *)manager
                 downloadTask:(NSURLSessionDownloadTask *)downloadTask
                       status:(TXSakuraDownloadTaskStatus)status;

// 主题下载完毕时回调,其中 infos 包括主题名称,可通过该参数直接切换至该主题
- (void)sakuraManagerDownload:(TXSakuraManager *)manager
                 downloadTask:(NSURLSessionDownloadTask *)downloadTask
                  sakuraInfos:(id<TXSakuraDownloadProtocol>)infos
    didFinishDownloadingToURL:(NSURL *)location;

// 主题下载进度
- (void)sakuraManagerDownload:(TXSakuraManager *)manager
                downloadTask:(NSURLSessionDownloadTask *)downloadTask
                didWriteData:(int64_t)bytesWritten
           totalBytesWritten:(int64_t)totalBytesWritten
   totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;

/** Reserved for future use */
- (void)sakuraManagerDownload:(TXSakuraManager *)manager
                downloadTask:(NSURLSessionDownloadTask *)downloadTask
           didResumeAtOffset:(int64_t)fileOffset
          expectedTotalBytes:(int64_t)expectedTotalBytes;

// 下载操作出现错误时回调
- (void)sakuraManagerDownload:(TXSakuraManager *)manager
                 sessionTask:(NSURLSessionTask *)downloadTask
        didCompleteWithError:(nullable NSError *)error;

// 主题下载包解压进度回调
- (void)sakuraManagerDownload:(TXSakuraManager *)manager
                 downloadTask:(NSURLSessionDownloadTask *)downloadTask
                progressEvent:(unsigned long long)loaded
                        total:(unsigned long long)total;

具体使用详见 SakuraDemo_OC,在 AppDelegate 中演示了该操作。

自定义下载操作

除了上述自带的下载操作外,SakuraKit 还提供了自定义下载操作相关的 API :


// sakuraModel 模型数据遵守了 TXSakuraDownloadProtocol 协议,location 即自定义下载下来的主题包地址。
[[TXSakuraManager manager] tx_generatePathWithInfos:sakuraModel downloadFileLocalURL:location successHandler:^(NSString *toFilePath, NSString *sakuraPath, TXSakuraName *sakuraName) {
                  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

      BOOL isSuccess = [SSZipArchive unzipFileAtPath:toFilePath toDestination:sakuraPath delegate:self];

      // 注意:自定义下载操作,必须进行 Sakura 路径格式化!Required!
      [TXSakuraManager formatSakuraPath:sakuraPath cleanCachePath:toFilePath];
      
      dispatch_sync(dispatch_get_main_queue(), ^{
          if (isSuccess) {
              [TXSakuraManager shiftSakuraWithName:sakuraName type:TXSakuraTypeSandBox];
          }
      });
   });
} errorHandler:^(NSError * _Nullable error) {
   NSLog(@"errorDescription:%@",error);
}];

FQA

1.为何每个主题都有自己配置文件?
答:由于每个主题,除了切图的命名是是一致的外,不同的主题背景色、字体大小可能不一样,因此,每个主题都要有自己的配置文件,除非只对切图进行本地化。

2.为何主题名称与配置文件名称一致?
答:这只是一个约定,SakuraKit 会通过主题名称找到该主题在本地或者在沙盒中的路径,使得主题名称与配置文件名称一致,可以减少不必要的工作量。

3.本地与沙盒主题有什么区别?
答:在本地主题称为 mainBundle 主题,远程主题称为 Sandbox 主题。

开源

关于 SakuraKit 具体使用,详见 Demo。

GitHub 项目地址:https://github.com/tingxins/SakuraKit

有什么问题或者更好的建议,GitHub 上直接提 issue 或者 PR。感谢支持

Demo 素材来源:网易云音乐等第三方 App,如有不妥之处,请及时联系并予以删除,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,358评论 6 343
  • 随着主持人的上场,节目终于开始正式进入录制。到底曾是电视台的招牌节目,即使在受冷落的情况下,节目水准还是比一般电视...
    小wo阅读 352评论 0 1
  • 启示1: 了解你的极限。意志力供给是有限的,从同一账户提取意志力用于各种不同任务,包括各类琐碎事情,等车等。 2:...
    西瓜弟的夏天阅读 901评论 0 1