换肤-Runtime - (Obj-C)

上一篇文章介绍了通过UIImage分类的方式实现换肤功能,但是此方式有一定的局限性:
需要开发前就考虑到这个功能,如果前期没有考虑到皮肤处理,开发中期或后期产品迭代,需求中添加这个功能,再去添加分类修改代码效率就太低了.这种情况下就可以通过Runtime机制来交换方法.

换肤-Runtime - (Obj-C)_第1张图片
exchangeImplementations.png

与基本换肤一样,在视图上通过ImageView显示不同的图片演示换肤功能,同时为了演示多界面同步换肤,设置一个TabBarController为根控制器,包含两个子控制器,如图:

换肤-Runtime - (Obj-C)_第2张图片
界面搭建.png

原理与基础换肤一样,仍然需要添加一个UIImage分类,在Load方法中通过运行时机制交换方法:

  • 首先导入头文件

  • 接下来通过运行时机制交换imageNamed和jsImageNamed方法

// 1. 获取对应交换的方法
Method method1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method method2 = class_getClassMethod([UIImage class], @selector(jsImageNamed:));
// 2. 交换方法
method_exchangeImplementations(method1, method2);
  • 外界直接使用系统方法imageNamed就可以了,运行时已经交换了两个方法的实现部分

#######需要注意的地方:
1.使用运行时机制交换方法,在App整个生命周期都会交换
2.使用交换方法后,注意死循环问题:
在自定义的方法中,调用了系统imageNamed方法,就会造成死循环(运行时交换的是两个方法的实现部分,当外界调用imageNamed的时候,实际执行的JSImageNamed的实现部分,这样方法内部调用了自己),但这里又要使用系统方法imageNamed,所以要换成自定义的jsImageNamed方法就可以了

// 自定义方法,根据当前皮肤设置图片
+ (UIImage *)jsImageNamed:(NSString *)name{
    
    if (isNight) { // 夜间模式
        
        name = [NSString stringWithFormat:@"%@_night",name];
    }
    
    return [UIImage jsImageNamed:name];
}

完整代码:
.h

#import 

@interface UIImage (JSSkin)

// 根据皮肤设置图片
+ (UIImage *)jsImageNamed:(NSString *)name;

// 记录皮肤
+ (void)saveSkinModeWithNight:(BOOL)night;

// 获取皮肤设置
+ (BOOL)isNight;

@end

.m

#import "UIImage+JSSkin.h"
#import 

@implementation UIImage (JSSkin)

// 夜间模式标识
static bool isNight;

+ (void)load{
    
    // 获取偏好设置中的皮肤模式
    isNight = [[NSUserDefaults standardUserDefaults] boolForKey:@"isNight"];
    
    // 使用运行时机制交换方法 一旦交换,在App整个生命周期都会交换
    // 1. 获取对应交换的方法
    Method method1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
    Method method2 = class_getClassMethod([UIImage class], @selector(jsImageNamed:));
    // 2. 交换方法
    method_exchangeImplementations(method1, method2);
    
}

// 自定义方法,根据当前皮肤设置图片
+ (UIImage *)jsImageNamed:(NSString *)name{
    
    if (isNight) { // 夜间模式
        
        name = [NSString stringWithFormat:@"%@_night",name];
    }
    
    return [UIImage jsImageNamed:name];
}

+ (void)saveSkinModeWithNight:(BOOL)night{
    
    // 赋值,记录当前皮肤状态
    isNight = night;
    
    // 本地记录状态(偏好设置)
    [[NSUserDefaults standardUserDefaults] setBool:isNight forKey:@"isNight"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
}

+ (BOOL)isNight{
    
    // 返回当前皮肤状态
    return isNight;
}

@end

外界调用:
控制器1

#import "PageOneViewController.h"
#import "UIImage+JSSkin.h"

@interface PageOneViewController ()

@property (weak, nonatomic) IBOutlet UIImageView *imageView_1;
@property (weak, nonatomic) IBOutlet UIImageView *imageView_2;
@property (weak, nonatomic) IBOutlet UISwitch *nightModeSwitch;

@end

@implementation PageOneViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 获取当前皮肤模式,同步Switch状态
    self.nightModeSwitch.on = [UIImage isNight];
    // 设置图片
    self.imageView_1.image = [UIImage imageNamed:@"baby"];
    self.imageView_2.image = [UIImage imageNamed:@"girl"];
    
}

// Switch开关点击事件
- (IBAction)nightModeSwitchClick:(UISwitch *)sender {
    
    // 本地化存储(偏好设置)
    [UIImage saveSkinModeWithNight:sender.isOn];
    
    // 设置图片
    self.imageView_1.image = [UIImage imageNamed:@"baby"];
    self.imageView_2.image = [UIImage imageNamed:@"girl"];
    
}

@end

控制器2

#import "PageTwoViewController.h"
#import "UIImage+JSSkin.h"

@interface PageTwoViewController ()

@property (strong, nonatomic) IBOutlet UIImageView *imageView_3;

@end

@implementation PageTwoViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    // 设置图片
    self.imageView_3.image = [UIImage imageNamed:@"girl"];

}

@end

效果图:

你可能感兴趣的:(换肤-Runtime - (Obj-C))