iOS 主题/皮肤之 SakuraKit

作者: tingxins | 来源:发表于2017-08-28 00:22 被阅读4115次
    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,如有不妥之处,请及时联系并予以删除,谢谢。

    相关文章

      网友评论

      • 流年小书:可以加个好友吗:smile:
      • MYS_iOS_8801:能否增加一个切换主题的block 回调,像这样的:
        _secondView.sakura.backgroundColor(Kbg_card)
        .addCustomConfig(@"", ^(id item) { // 改变主题的切换
        NSString *colorStr = [TXSakuraManager getThemeColorString:Kfont_strong];
        _secondView.rateColor = HexColor(colorStr);
        });
      • __block:你好, 我想问下怎么支持 attributesText ?
      • 运气江:[[UIImage imageNamed:imageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]我需要加载原图navigationController.tabBarItem.sakura.image(imageName),需要怎么写?
        f6ee6c2d7437:@tingxins 找到问题了,我把[self.window makeKeyAndVisible]写在了[TXSakuraManager shiftSakuraWithName:name type:type]前面了
        f6ee6c2d7437:@tingxins [childVc.tabBarItem.sakura
        .image(image)
        .selectedImage(selectedImage)
        .titleTextAttributes(@"Global.tabTitleAttributeColor",UIControlStateNormal)
        .titleTextAttributes(@"Global.tabTitleAttributeSelectColor",UIControlStateSelected)
        setImageRenderingMode:UIImageRenderingModeAlwaysOriginal];

        您好,问下我这样写有问题吗?为什么我的tabbar的图片还是渲染的蓝色的?
        tingxins:@2c9ab8a3dadf TXSakura 有一个 imageRenderingMode 可以设置的
      • ChardXu:还有,您这个主题支持渐变色吗?不支持的话就尴尬了
      • ChardXu:pod search SakuraKit 搜不到,提示[!] Unable to find a pod with name, author, summary, or description matching `SakuraKit`
        ChardXu:@tingxins
        static NSString *const kTXSakuraCurrentName = @"com.tingxins.sakura.current.name";
        static NSString *const kTXSakuraCurrentType = @"com.tingxins.sakura.current.type";
        static NSString *const kTXSakuraManagerLockName = @"com.tingxins.sakura.manager.lock";
        这几个值要改下吧?
        tingxins:建议清理一下 本地 search index。之前有童鞋问过类似的问题,可以查一下评论历史哈
        ChardXu:pod install 可以,search不行
      • Persistent丧心病狂:现在下载不了主题额
        tingxins:@好好学习_我说的是一辈子 还得稍等几天,等数据迁移……
        Persistent丧心病狂:@tingxins 现在打开呗 🤣
        tingxins:@好好学习_我说的是一辈子 下载不了是因为假期有人恶意刷流量,我已经关了
      • brilliance_Liu:楼主还没支持pod?
        tingxins:@brilliance_Liu 支持的。 (http://cocoadocs.org/docsets/SakuraKit)
      • Torin76: [TXSakuraManager registerLocalSakuraWithNames:@[@"typewriter"]];

        NSString *name = [TXSakuraManager getSakuraCurrentName];
        NSInteger type = [TXSakuraManager getSakuraCurrentType];
        [TXSakuraManager shiftSakuraWithName:name type:type];
        注册默认的后,为什么name还是为nil
        ChardXu:@tingxins 我也是这个问题,急需求解
        tingxins:@Torin76 ummmh......,registerLocalSakuraWithNames 与 getSakuraCurrentName 两个方法并无直接关系的,目前这个版本注册的目的仅仅是作用于 tx_getSakurasList 方法。如果想使用默认主题,直接使用 [TXSakuraManager shiftSakuraWithName: kTXSakuraDefault type: type] 即可,如果想使用其他主题(本地或远程都可)[TXSakuraManager shiftSakuraWithName: [主题名称] type: type]。详见 Demo (https://github.com/tingxins/SakuraKit/tree/master/SakuraDemo/SakuraDemo_OC)
        Torin76:跪求楼主解答下,看了一天代码了,找不出原因。
      • Torin76:@"http://rapapi.org/mockjsdata/21815/theme/list&quot; 楼主你的demo中这个接口没数据返回,无法请求数据
        Torin76:@tingxins 可以了。哈哈
        tingxins:@Torin76 可能是mock服务不稳定,等会就可以了的
        Torin76:http://rapapi.org/mockjsdata/21815/theme/list 这个
      • 默默_David:楼主,我们平时开发的时候,有@1x,@2X,@3x三种图片,不同机型应用不同的图片,你主题包上传的时候,是否要针对不同机型上传不同的主题包?
        tingxins:@默默_David 自动处理的呀。只要 1x/2x/3x 在同一目录即可。
        默默_David:也就是说在代码中已经控制了不同机型选择对应的图片了是吧?
        tingxins:@默默_David 不需要针对不同机型上传不同主题包。每个主题包中包含 1x/2x/3x 的图即可。
      • 星好唯柔:如果这个库设置能转移到xib慢板或者storyboard面板上,就可以了!!
        Persistent丧心病狂:@tingxins 实现一下呗
        tingxins:@星好唯柔 好想法:+1:
      • 梦里风吹过:之前有弄过这一块的东西,有一种蛮有意思的处理方式,我觉得这种方式处理起来挺不错的,尤其是在项目做了一半的时候加模式切换,而且有很大的灵活性.http://www.jianshu.com/p/9aa0b76f55b3有啥意见可以交流下啊
        tingxins:@梦星Chen :+1:
      • 44d3387e09f3:项目正需要这个东西,感谢
        Persistent丧心病狂:你们的主题做了么,用了多久做出炉的
      • 三清渡:看代码看的脑壳疼....:joy:
        brilliance_Liu:确实看的脑袋疼:sweat:
      • 知傲:更喜欢SwiftTheme的扩展方式,需要设置哪个属性在前面加上theme_就能找到
        知傲:@tingxins 扩展性方面怎么样?
        tingxins:@zhao0 给某些控件主题化时一般熟悉其属性呀,sakura.xxxx即可。当然采用sakura.tx_xxxx格式可考虑:smile:
      • 五分钟学算法:需要xcode 9才能运行:joy:
        coderChrisLee:是的。Xcode9才行。
        五分钟学算法:@tingxins 幸好有xcode 9 跑了一下可以运行 开始看代码:+1:
        tingxins:@MisterBooo 回头把xib换一下:turtle:

      本文标题:iOS 主题/皮肤之 SakuraKit

      本文链接:https://www.haomeiwen.com/subject/affwdxtx.html