说到主题切换,那么久要做到切换主题瞬间,使所有相关的界面都发生变化,这就需要一种机制来将主题切换这是事件跑出来,并且接受主题切换事件的相关View 做出相应的改变。想到这里你肯定也想到了NSNotification。没错,这就是个不错的选择,很适合我们的场景。下面具体来实现下。
不管是本地换肤还是动态换肤都需要一个Manager 进行初始化主题模式,一半情况下都使用单例初始化就可以。
YNThemeManager.h
主要提供这几个方法:
-
(void)setupThemeNameArray:(NSArray *)array;
是用来初始化主题模式名称的, 例如我们初始化两个本地资源文件 YNTheme-White 和 YNTheme-Black 是bundle文件名称
[[YNThemeManager sharedInstance] setupThemeNameArray:@[@"YNTheme-White", @"YNTheme-Black"]];
-- (BOOL)changeTheme:(NSString *)themeName;
用来改变主题模式的,在实际使用中只需要将已有的bundle名称传入即可
[[YNThemeManager sharedInstance] changeTheme:@"YNTheme-White"];
-
+ (UIColor *)colorWithID:(NSString *)colorID;
用来获取颜色 -
+ (UIImage *)imageWithName:(NSString *)imageName;
用来获取图片
YNThemeManager.m
1.初始化
+ (instancetype)sharedInstance{
static YNThemeManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[YNThemeManager alloc] init];
});
return manager;
}
2.首先申明几个属性
bundle colorsMap themeArray
/** 主题bundle*/
@property (nonatomic,strong) NSBundle *bundle;
/** 颜色对照表*/
@property (nonatomic, copy) NSDictionary *colorsMap;
/** 主题数组*/
@property (nonatomic, copy) NSArray *themeArray;
3.主题数组赋值
- (void)setupThemeNameArray:(NSArray *)array{
self.themeArray = array;
}
4.改变主题.m实现
- (BOOL)changeTheme:(NSString *)themeName{
/** 判断当前切换主题是否在主题数组中*/
if (![_themeArray containsObject:themeName]) {
return NO;
}
/** 获取bundle路径*/
NSBundle *bundle = [NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:themeName withExtension:@"bundle"]];
if (!bundle) {
return NO;
}
/** 获取bundle下plist文件路径*/
NSString *mapPath = [bundle pathForResource:@"ColorsMap" ofType:@"plist"];
if (!mapPath) {
return NO;
}
/** 获取字典*/
NSDictionary *colorsMap = [NSDictionary dictionaryWithContentsOfFile:mapPath];
/** 赋值*/
_themeName = themeName;
self.bundle = bundle;
self.colorsMap = colorsMap;
/** 发送修改通知*/
[self sendChangeThemeNotification];
return YES;
}
/** 发送修改通知*/
- (void)sendChangeThemeNotification {
[[NSNotificationCenter defaultCenter] postNotificationName:YNThemeChangeNotification object:nil];
}
5.获取颜色
+ (UIColor *)colorWithID:(NSString *)colorID{
if (!colorID) {
return [UIColor clearColor];
}
return [UIColor yn_colorWithHexString:[[self class] colorStringWithID:colorID]];
}
/** 用来查找plist 文件中对应色值的value */
+ (NSString *)colorStringWithID:(NSString *)colorID{
NSArray *array = [colorID componentsSeparatedByString:@"_"];
NSAssert(array.count > 1, @"未找到对应颜色-%@", colorID);
NSDictionary *colorDict = [[YNThemeManager sharedInstance].colorsMap valueForKeyPath:array[0]];
NSString *value = colorDict[colorID][@"Color"];
NSAssert(value, @"未找到对应颜色-%@", colorID);
return value;
}
6.获取图片
+ (UIImage *)imageWithName:(NSString *)imageName {
if (!imageName) {
return nil;
}
NSBundle *bundle = [YNThemeManager sharedInstance].bundle;
UIImage *image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil];
NSAssert(image, @"未找到对应图片-%@", imageName);
return image;
}
- 首先,控制器中的控件比较多,改变起来逻辑相当复杂,逻辑可能不是很清楚
- 其次就是VC 中有些View 有很多层次,如;VC 中有一个HeaderView ,HeaderView中有BlackView,BlackView 中又有ImageView ,ImageView 中可能还有其他控件,如果要是在主题切换时改变ImageView,面临的问题就是
VC ---->HeaderView -----> BlackView ---->ImageView
这么长的一个通知链。估计写起来会忍不住吐槽。同时维护起来也是很大的问题。
基于以上问题,我改变了设计思路,决定采用系统控件主动接受通知。因此想到了对控件做手脚,以Label为例,为UILabel搞一个主题扩展
- 大家可以看到其中有换肤属性theme_textColor ,如下图,我们在属性theme_textColor 的Setter方法中有根据主题配置调用系统的相应方法,然后对控件注册监听,等切换主题之后就会收到通知,然后执行theme_didChanged方法,为控件设置正确的主题UI下面直接上代码:
#import
NS_ASSUME_NONNULL_BEGIN
@interface UILabel (YNTheme)
@property (nonatomic, copy) NSString *theme_textColor;
@property (nonatomic, copy) NSAttributedString *theme_attributedText;
@end
NS_ASSUME_NONNULL_END
@implementation UILabel (YNTheme)
- (void)theme_didChanged {
[super theme_didChanged];
if (self.theme_textColor) {
self.textColor = [YNThemeManager colorWithID:self.theme_textColor];
}
if (self.attributedText) {
self.attributedText = self.attributedText.theme_replaceRealityColor;
}
}
// MARK: ================ Setters ===========================
- (void)setTheme_textColor:(NSString *)color {
self.textColor = [YNThemeManager colorWithID:color];
objc_setAssociatedObject(self, @selector(theme_textColor), color, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self theme_registChangedNotification];
}
- (void)setTheme_attributedText:(NSAttributedString *)attributedText {
self.attributedText = attributedText.theme_replaceRealityColor;
[self theme_registChangedNotification];
}
- (void)setSDTextColorID:(NSString *)SDTextColorID {
self.theme_textColor = SDTextColorID;
}
// MARK: ================ Getters ===========================
- (NSString *)theme_textColor {
return objc_getAssociatedObject(self, @selector(theme_textColor));
}
- (NSAttributedString *)theme_attributedText {
return self.attributedText;
}
@end
- 当然这里面会用到通知,我们专门创建一个
NSObject+YNTheme
分类,用于通知管理,废话不多说,直接上代码。
#import
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (YNTheme)
/**
注册换肤监听,不会重复监听
收到通知后会调用 theme_didChanged 方法
*/
- (void)theme_registChangedNotification;
/**
注册换肤监听,不会重复监听
会立即调用一次 themeChangeBlock,和收到通知后调用
*/
- (void)theme_observerChangedUsingBlock:(void(^)(id observer))themeChangeBlock;
/** 子类重写,收到换肤通知会调用本方法*/
- (void)theme_didChanged;
@end
NS_ASSUME_NONNULL_END
#import "NSObject+YNTheme.h"
#import "YNThemeManager.h"
#import
#import "NSObject+YNDeallocExecutor.h"
static NSString *const kHasRegistChangedThemeNotification;
@interface NSObject ()
@property (nonatomic, copy) void(^theme_changeBlock)(id observer);
@end
@implementation NSObject (YNTheme)
- (void)theme_registChangedNotification {
NSNumber *hasRegist = objc_getAssociatedObject(self, &kHasRegistChangedThemeNotification);
/** 标识是否已经注册通知,防止多次设置后导致同一个控件被注册多次*/
if (hasRegist) {
return;
}
objc_setAssociatedObject(self, &kHasRegistChangedThemeNotification, @(YES), OBJC_ASSOCIATION_COPY_NONATOMIC);
/** 接收通知*/
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(theme_didChanged) name:YNThemeChangeNotification object:nil];
/** 暂时不明白*/
__weak typeof(self) weakSelf = self;
[self yn_executeAtDealloc:^{
[[NSNotificationCenter defaultCenter] removeObserver:weakSelf];
}];
}
- (void)theme_observerChangedUsingBlock:(void(^)(id observer))themeChangeBlock {
self.theme_changeBlock = themeChangeBlock;
[self theme_didChanged];
[self theme_registChangedNotification];
}
- (void)theme_didChanged {
if (self.theme_changeBlock) {
__weak typeof(self) weakSelf = self;
self.theme_changeBlock(weakSelf);
}
}
- (void)setTheme_changeBlock:(void (^)(void))block {
objc_setAssociatedObject(self, @selector(theme_changeBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void (^)(void))theme_changeBlock {
return objc_getAssociatedObject(self, @selector(theme_changeBlock));
}
@end
- 不知道大家发现没有这里面涉及到一个 block回调方法
yn_executeAtDealloc
这里面具体做什么,容我细细道来。 - 我们在开发过程经常会遇到这样的情况,我们想监测一个NSObject对象到底有没有释放掉,通常的做法就是继承于一个父类在其dealloc方法中进行NSLog打印输出了,这时候我们有没有思考可以很方便的去实现dealloc方法的捕获?下面和大家分享一个简单的方法,来实现这个过程,废话不多说直接上代码。
#import
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (YNDeallocExecutor)
- (void)yn_executeAtDealloc:(void (^)(void))block;
@end
NS_ASSUME_NONNULL_END
#import "NSObject+YNDeallocExecutor.h"
#import
const void *YNDeallocExecutorsKey = &YNDeallocExecutorsKey;
@interface YNDeallocExecutor : NSObject
@property (nonatomic, copy) void(^deallocExecutorBlock)(void);
@end
@implementation YNDeallocExecutor
- (id)initWithBlock:(void(^)(void))deallocExecutorBlock {
self = [super init];
if (self) {
_deallocExecutorBlock = [deallocExecutorBlock copy];
}
return self;
}
- (void)dealloc {
_deallocExecutorBlock ? _deallocExecutorBlock() : nil;
}
@end
@implementation NSObject (YNDeallocExecutor)
- (void)yn_executeAtDealloc:(void (^)(void))block{
if (block) {
YNDeallocExecutor *executor = [[YNDeallocExecutor alloc] initWithBlock:block];
/** 创建一个互斥锁,保证在同一时间内没有其它线程对self对象进行修改,起到线程的保护作用*/
@synchronized (self) {
[[self hs_deallocExecutors] addObject:executor];
}
}
}
- (NSHashTable *)hs_deallocExecutors {
NSHashTable *table = objc_getAssociatedObject(self,YNDeallocExecutorsKey);
if (!table) {
table = [NSHashTable hashTableWithOptions:NSPointerFunctionsStrongMemory];
objc_setAssociatedObject(self, YNDeallocExecutorsKey, table, OBJC_ASSOCIATION_RETAIN);
}
return table;
}
@end