iOS主题设计

iOS主题

第一种方案思路:

业务方:
  • 添加监听主题变更通知, 在通知方法里面进行重置主题
主题管理者业务:
  • 主题的配置
  • 发送主题变更通知

第二种方案思路
业务方:
  • 配置自己业务的各个主题的色值和资源
  • 向管理员注册切换主题的回调Block/Selector,在回调里面重置主题
主题管理者业务:
  • 集合去保存每一个业务的回调,key为业务本身, value为回调Block/Selector
  • 变更主题去触发所有注册的业务回调

第三种方案( 不建议 )
直接替换主工程window的root控制器,直接全部重新创建

优缺点分析
  • 第一种方案需要业务去注册通知, 代码优化可采用分类的形式添加注册和注销通知的快捷方法,方便编码. 诟病也在这里需要自己去注册之后再写响应通知的方法

  • 第二种方案需要每一个使用主题的地方都需要去设置自己的主题配置, 业务方使用起来太麻烦, 组件化之后后期需要改动的地方太多

合并一下前两种方案

采用第一种管理层做法: 由管理层去管理主题的配置, 并增加第二种管理层的服务: 注册回调功能

这样之后的方案就是

业务方:
  • 向管理员注册切换主题的回调Block/Selector,在回调里面重置主题
主题管理层:
  • 主题的配置
  • 集合去保存每一个业务的回调,key为业务本身, value为回调Block/Selector
  • 变更主题去触发所有注册的业务回调
这样之后业务方使用方便, 组件化采用协议注册形式达到解耦主题管理者和业务

代码思路图示

image-20211214102528845.png

由上图导出下图具体代码

image-20211214102517079.png

代码讲解:

数据协议

基础主题协议: 这是定义业务使用主题的字段
 @protocol ThemeBaseStyleProtocol <NSObject>
 
 /// tabbar的背景颜色
 @property (nonatomic , strong) UIColor *tabbar_bg_color;
 /// navigation背景颜色
 @property (nonatomic , strong) UIColor *navigation_bg_color;
 /// tabbar未读数的背景颜色
 @property (nonatomic , strong) UIColor *tabbar_unread_num_bg_color;
 /// tabbar未读数的文案颜色
 @property (nonatomic , strong) UIColor *tabbar_unread_num_title_color;
 
 /// 分??? #pragma mark - Home
 /// 首页选项文字颜色
 @property (nonatomic , strong) UIColor *home_navigation_enum_title_color;
 /// 首页选项文字选中颜色
 @property (nonatomic , strong) UIColor *home_navigation_enum_selected_title_color;
 /// 首页选项文案右边的箭头默认图片
 @property (nonatomic , strong) NSString *home_navigation_enum_normal_arrow_image_name;
 /// 首页选项文案右边的箭头选中图片
 @property (nonatomic , strong) NSString *home_navigation_enum_selected_arrow_image_name;
 /// 首页选项滑动条颜色
 @property (nonatomic , strong) UIColor *home_navigation_enum_silder_color;
 /// 首页右上角加号图片name
 @property (nonatomic , strong) NSString *home_navigation_right_add_img_name;
 
 #pragma mark - Service
 /// 顶部标题颜色
 @property (nonatomic , strong) UIColor *service_navigation_title_color;
 @property (nonatomic , strong) UIColor *service_navigation_subtitle_color;
 @property (nonatomic , strong) NSString *service_navigation_arrow_image_name;
 
 #pragma mark - Partner
 
 #pragma mark - MAll
 /// 商场的顶部大标题颜色
 @property (nonatomic , strong) UIColor *mall_navigation_left_title_color;
 /// 顶部右边搜索按钮图片
 @property (nonatomic , strong) NSString *mall_navigation_right_search_image_name;
 /// 顶部右边购物车按钮图片
 @property (nonatomic , strong) NSString *mall_navigation_right_shopping_cart_image_name;
 /// 顶部购物车数值颜色
 @property (nonatomic , strong) UIColor *mall_navigation_shoping_num_color;
 /// 顶部购物车数值背景颜色
 @property (nonatomic , strong) UIColor *mall_navigation_shoping_num_bg_color;
 
 #pragma mark - My
 /// 顶部背景图片
 @property (nonatomic , strong) NSString *my_navigation_top_bg_image_name;
 /// 人头装饰
 @property (nonatomic , strong) NSString *my_header_decoration_image_name;
 
 /// 预留给网络数据转化为本地数据
 - (instancetype)initWithThemeConfig:(NSDictionary *)config;
 
 @end
主题扩展协议: 这里是为了给主题配置一个包装层, 供扩展用,例如远程数据和本地数据的相互兼容
@interface ThemeItemModel : NSObject
 
 @property (nonatomic , strong) NSString *themeId;
 
 @property (nonatomic , strong) NSString *title;
 
 @property (nonatomic , strong) NSString *img;
 
 @property (nonatomic , strong) id<ThemeBaseStyleProtocol> theme;
 
 @end

管理者

管理者协议
  /// 主题
 @protocol ThemeProtocol <NSObject>
 
 /// 当前所有的皮肤
 @property (nonatomic , strong) NSMutableArray <ThemeItemModel *> *allThemes;
 
 /// 当前的主题, 默认: ThemeDetaultStyle
 @property (nonatomic , strong, readonly) id<ThemeBaseStyleProtocol>currentTheme;
 
 /// 注册主题改变之后的回调
 /// 注册可以采用分类方法, 写了快捷方式
 /// 想要删除回调,可设置回调为nil即可
 /// 不会对obj进行强引用, 外界obj释放掉之后,里面对应的回调也会销毁
 /// 内部会先自动调用一次这个回调
 /// @param obj 响应者
 /// @param callback 回调
 - (void)regisObserver:(id _Nonnull)obj themeDidChangeCallBack:(void(^ _Nullable)(id<ThemeBaseStyleProtocol> theme))callback;
 
 /// 更新皮肤配置, 从后台获取是否有新的皮肤, 以保存到本地区使用, 本地预存了两份
 - (void)updateConfigTheme;
 
 /// 更换皮肤
 - (void)changeTheme:(ThemeItemModel *)themeItemModel;
 
 @end
快捷业务方法
@interface UIView (ThemeCategory)
 
 - (void)regisThemeDidChangeCallBack:(void(^)(id<ThemeBaseStyleProtocol> theme))callback;
 
 @end
 
 @interface UIViewController (ThemeCategory)
 
 - (void)regisThemeDidChangeCallBack:(void(^)(id<ThemeBaseStyleProtocol> theme))callback;
 
 @end
 
 @implementation UIView (ThemeCategory)
 
 - (void)regisThemeDidChangeCallBack:(void(^)(id<ThemeBaseStyleProtocol> theme))callback{

     id <ThemeProtocol>theme = 获取到实现ThemeProtocol 协议的实现者, 必须单类;

    [theme regisObserver:self themeDidChangeCallBack:callback];
 }
 
 @end
 
 @implementation UIViewController (ThemeCategory)
 
 - (void)regisThemeDidChangeCallBack:(void(^)(id<ThemeBaseStyleProtocol> theme))callback{

     id <ThemeProtocol>theme = 获取到实现ThemeProtocol 协议的实现者, 必须单类;

    [theme regisObserver:self themeDidChangeCallBack:callback];
 }
 
 @end

生成协议之后,基本上数据流已经通了

数据流走向?
① 程序启动,配置主题
② 业务(UIView/UIViewController)去注册协议,
③当主题发生变化的时候,调用主题管理者的changeTheme 传入当前要改变的主题, 主题管理者会触发注册的所有回调, 业务方在回调里面根据回调的当前主题去设置新的UI样式, 如图
image-20211214104635144.png

管理者实现代码

①配置数据
 /// 配置主题
 - (void)updateConfigTheme; {

    [self.allThemes removeAllObjects];
     // 默认
     ThemeItemModel *defaultModel = [ThemeItemModel new];
     defaultModel.title = @"默认";
     defaultModel.themeId = @"18c63459a2c069022c7790430f761214";// 默认 MD5
   //ThemeDetaultStyle 这个就是实现了主题基础协议的数据类,返回了主题的颜色和图片
     defaultModel.theme = [ThemeDetaultStyle new];
    [self.allThemes addObject:defaultModel];
     _currentTheme = defaultModel.theme;
     // 新年
     ThemeItemModel *yearModel = [ThemeItemModel new];
     yearModel.title = @"新年";
     yearModel.themeId = @"d46d5bb15db17203e4e5d61372325f91";// 新年 MD5
   //ThemeNewYearStyle 这个就是实现了主题基础协议的数据类,返回了主题的颜色和图片
     yearModel.theme = [ThemeNewYearStyle new];
    [self.allThemes addObject:yearModel];

     // 先看本地是否有保存皮肤
     NSString *saveKey = [[NSUserDefaults standardUserDefaults] objectForKey:ThemeSaveKey];

     if ([saveKey isKindOfClass:[NSString class]]) {
         for (ThemeItemModel * item in self.allThemes) {
             if ([item.themeId isEqualToString:saveKey]) {
                 _currentTheme = item.theme;
                 break;
            }
        }
    }

     /// 请求接口
   这里根据自己需求做
 }

②注册回调

/注: 利用NSMapTable 去保存回调来达到对key的弱引用, 来确保已经释放的类不会进行去调用回调/

- (void)regisObserver:(id)obj themeDidChangeCallBack:(void (^)(id<AWThemeBaseStyleProtocol> theme))callback {

     if (obj) {

         // 调用一次
         if (callback) {
             callback(_currentTheme);
        }

        [self.observers setObject:callback forKey:obj];
    }
 }
 
 - (NSMapTable *)observers {
     if (!_observers) {
         _observers = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsWeakMemoryvalueOptions:NSPointerFunctionsCopyIn capacity:0];;
    }
     return _observers;
 }

③ 更换主题
 /// 更换皮肤
 - (void)changeTheme:(AWThemeItemModel *)themeItemModel {

     if (_currentTheme == themeItemModel.theme) {
         return;
    }

    [[NSUserDefaults standardUserDefaults] setObject:themeItemModel.themeIdforKey:ThemeSaveKey];
    [[NSUserDefaults standardUserDefaults] synchronize];

     _currentTheme = themeItemModel.theme;

     // 回调所有的注册的回调
     if (self.observers.count) {
         NSEnumerator *enumerator = self.observers.objectEnumerator;
         void (^callback)(id<AWThemeBaseStyleProtocol> theme) = nil;
         while (callback = [enumerator nextObject]) {
             callback(_currentTheme);
        }
    }
 }

至此已完成主题相关操作, 接下来配置好数据之后, 业务仓向管理者注册回调即可达到主题效果

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

推荐阅读更多精彩内容

  • MVC: Model: 负责存储,定义,操作数据;View: 用来展示给用户数据,和用户进行交互操作的;Contr...
    西风颂阅读 738评论 4 20
  • 1.网络 1.网络七层协议有哪些? 物理层:主要功能:传输比特流;典型设备:集线器、中继器;典型协议标准和应用:V...
    _我和你一样阅读 3,391评论 1 38
  • 1、APP的启动过程、main函数? 内核初始化空间创建进程-》加载解析执行文件 - 》载入动态链接器(加载依赖库...
    032c6843a285阅读 756评论 2 12
  • iOS项目架构 做了几个App,发现很多时候,App的基本框架都是一样的,如何组织架构,让项目更容易开发和维护,减...
    苦笑男神阅读 1,774评论 8 9
  • 1、登录(文本输入、按钮交互、基于网络的交互) 2、刷新界面:(表视图) 1>小部分应用程序数据来源于本地 2>更...
    炙冰阅读 762评论 0 1