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);
}
}
}