iOS主题设计
2021-12-14 本文已影响0人
kalpa_shock
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);
}
}
}