iOS主題
第一種方案思路:
業(yè)務(wù)方:
- 添加監(jiān)聽主題變更通知, 在通知方法里面進行重置主題
主題管理者業(yè)務(wù):
- 主題的配置
- 發(fā)送主題變更通知
第二種方案思路
業(yè)務(wù)方:
- 配置自己業(yè)務(wù)的各個主題的色值和資源
- 向管理員注冊切換主題的回調(diào)Block/Selector,在回調(diào)里面重置主題
主題管理者業(yè)務(wù):
- 集合去保存每一個業(yè)務(wù)的回調(diào),key為業(yè)務(wù)本身, value為回調(diào)Block/Selector
- 變更主題去觸發(fā)所有注冊的業(yè)務(wù)回調(diào)
第三種方案( 不建議 )
直接替換主工程window的root控制器,直接全部重新創(chuàng)建
優(yōu)缺點分析
第一種方案需要業(yè)務(wù)去注冊通知, 代碼優(yōu)化可采用分類的形式添加注冊和注銷通知的快捷方法,方便編碼. 詬病也在這里需要自己去注冊之后再寫響應(yīng)通知的方法
第二種方案需要每一個使用主題的地方都需要去設(shè)置自己的主題配置, 業(yè)務(wù)方使用起來太麻煩, 組件化之后后期需要改動的地方太多
合并一下前兩種方案
采用第一種管理層做法: 由管理層去管理主題的配置, 并增加第二種管理層的服務(wù): 注冊回調(diào)功能
這樣之后的方案就是
業(yè)務(wù)方:
- 向管理員注冊切換主題的回調(diào)Block/Selector,在回調(diào)里面重置主題
主題管理層:
- 主題的配置
- 集合去保存每一個業(yè)務(wù)的回調(diào),key為業(yè)務(wù)本身, value為回調(diào)Block/Selector
- 變更主題去觸發(fā)所有注冊的業(yè)務(wù)回調(diào)
這樣之后業(yè)務(wù)方使用方便, 組件化采用協(xié)議注冊形式達到解耦主題管理者和業(yè)務(wù)
代碼思路圖示

image-20211214102528845.png
由上圖導出下圖具體代碼

image-20211214102517079.png
代碼講解:
數(shù)據(jù)協(xié)議
基礎(chǔ)主題協(xié)議: 這是定義業(yè)務(wù)使用主題的字段
@protocol ThemeBaseStyleProtocol <NSObject>
/// tabbar的背景顏色
@property (nonatomic , strong) UIColor *tabbar_bg_color;
/// navigation背景顏色
@property (nonatomic , strong) UIColor *navigation_bg_color;
/// tabbar未讀數(shù)的背景顏色
@property (nonatomic , strong) UIColor *tabbar_unread_num_bg_color;
/// tabbar未讀數(shù)的文案顏色
@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;
/// 頂部購物車數(shù)值顏色
@property (nonatomic , strong) UIColor *mall_navigation_shoping_num_color;
/// 頂部購物車數(shù)值背景顏色
@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;
/// 預(yù)留給網(wǎng)絡(luò)數(shù)據(jù)轉(zhuǎn)化為本地數(shù)據(jù)
- (instancetype)initWithThemeConfig:(NSDictionary *)config;
@end
主題擴展協(xié)議: 這里是為了給主題配置一個包裝層, 供擴展用,例如遠程數(shù)據(jù)和本地數(shù)據(jù)的相互兼容
@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
管理者
管理者協(xié)議
/// 主題
@protocol ThemeProtocol <NSObject>
/// 當前所有的皮膚
@property (nonatomic , strong) NSMutableArray <ThemeItemModel *> *allThemes;
/// 當前的主題, 默認: ThemeDetaultStyle
@property (nonatomic , strong, readonly) id<ThemeBaseStyleProtocol>currentTheme;
/// 注冊主題改變之后的回調(diào)
/// 注冊可以采用分類方法, 寫了快捷方式
/// 想要刪除回調(diào),可設(shè)置回調(diào)為nil即可
/// 不會對obj進行強引用, 外界obj釋放掉之后,里面對應(yīng)的回調(diào)也會銷毀
/// 內(nèi)部會先自動調(diào)用一次這個回調(diào)
/// @param obj 響應(yīng)者
/// @param callback 回調(diào)
- (void)regisObserver:(id _Nonnull)obj themeDidChangeCallBack:(void(^ _Nullable)(id<ThemeBaseStyleProtocol> theme))callback;
/// 更新皮膚配置, 從后臺獲取是否有新的皮膚, 以保存到本地區(qū)使用, 本地預(yù)存了兩份
- (void)updateConfigTheme;
/// 更換皮膚
- (void)changeTheme:(ThemeItemModel *)themeItemModel;
@end
快捷業(yè)務(wù)方法
@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 = 獲取到實現(xiàn)ThemeProtocol 協(xié)議的實現(xiàn)者, 必須單類;
[theme regisObserver:self themeDidChangeCallBack:callback];
}
@end
@implementation UIViewController (ThemeCategory)
- (void)regisThemeDidChangeCallBack:(void(^)(id<ThemeBaseStyleProtocol> theme))callback{
id <ThemeProtocol>theme = 獲取到實現(xiàn)ThemeProtocol 協(xié)議的實現(xiàn)者, 必須單類;
[theme regisObserver:self themeDidChangeCallBack:callback];
}
@end
生成協(xié)議之后,基本上數(shù)據(jù)流已經(jīng)通了
數(shù)據(jù)流走向?
① 程序啟動,配置主題
② 業(yè)務(wù)(UIView/UIViewController)去注冊協(xié)議,
③當主題發(fā)生變化的時候,調(diào)用主題管理者的changeTheme 傳入當前要改變的主題, 主題管理者會觸發(fā)注冊的所有回調(diào), 業(yè)務(wù)方在回調(diào)里面根據(jù)回調(diào)的當前主題去設(shè)置新的UI樣式, 如圖

image-20211214104635144.png
管理者實現(xiàn)代碼
①配置數(shù)據(jù)
/// 配置主題
- (void)updateConfigTheme; {
[self.allThemes removeAllObjects];
// 默認
ThemeItemModel *defaultModel = [ThemeItemModel new];
defaultModel.title = @"默認";
defaultModel.themeId = @"18c63459a2c069022c7790430f761214";// 默認 MD5
//ThemeDetaultStyle 這個就是實現(xiàn)了主題基礎(chǔ)協(xié)議的數(shù)據(jù)類,返回了主題的顏色和圖片
defaultModel.theme = [ThemeDetaultStyle new];
[self.allThemes addObject:defaultModel];
_currentTheme = defaultModel.theme;
// 新年
ThemeItemModel *yearModel = [ThemeItemModel new];
yearModel.title = @"新年";
yearModel.themeId = @"d46d5bb15db17203e4e5d61372325f91";// 新年 MD5
//ThemeNewYearStyle 這個就是實現(xiàn)了主題基礎(chǔ)協(xié)議的數(shù)據(jù)類,返回了主題的顏色和圖片
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;
}
}
}
/// 請求接口
這里根據(jù)自己需求做
}
②注冊回調(diào)
/注: 利用NSMapTable 去保存回調(diào)來達到對key的弱引用, 來確保已經(jīng)釋放的類不會進行去調(diào)用回調(diào)/
- (void)regisObserver:(id)obj themeDidChangeCallBack:(void (^)(id<AWThemeBaseStyleProtocol> theme))callback {
if (obj) {
// 調(diào)用一次
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;
// 回調(diào)所有的注冊的回調(diào)
if (self.observers.count) {
NSEnumerator *enumerator = self.observers.objectEnumerator;
void (^callback)(id<AWThemeBaseStyleProtocol> theme) = nil;
while (callback = [enumerator nextObject]) {
callback(_currentTheme);
}
}
}