iOS应用主题(图片,颜色)统一管理


// 2017.8.24 更新
进阶篇:再谈 Swift 换肤功能,看完该文后强烈建议看下进阶篇,彩蛋彩蛋彩蛋哦。


在我过去的一个多月里,发现很多不愉快的事情,导致 OpenGL SE系列的文章好久没有更新了,今天来分享下以前做的主题管理来做一个新开始,OpenGL SE的文章会继续坚持写下去,欢迎关注。

只需@3x图片

现在工作改做SDK后,发现很少和界面相关的东西打交道了,但做过APP的同学们都应该知道,为了适应各种屏幕的尺寸,图片资源需要提供@1x、@2x和@3x来适配屏幕界面,现在基本没有 1x屏幕的设备了,可以不用提供这个分辨率的图片了。但@2x和@3x可以说是重复的资源,这只会增大应用包的大小。


C69924BD-2C0E-4279-A4EB-3D822C5AB340.png

在这,我只使用@3x的图片来做适配:
先写在前面,iOS 8后系统自动会将@3x图片自动适配图片,也就是说你的应用不支持iOS 8以下系统的话,你可以直接使用@3x的资源就可以了,你可以直接跳过这一节。

这里为了实现换肤功能,所有的资源我都会存在Bundle里面,首先解释下,Bundle是静态的,作为一个资源包是不参加项目编译的,也就是说,bundle包中不能包含可执行的文件,它仅仅是作为资源,被解析成为特定的2进制数据。对于在iOS 8系统上会自动将@3x的资源自动适配后,我们只需要考虑iOS 8下的系统,这个时候我们只需要手动去重新绘制图片的大小(比较消耗性能的动作),实现如下:

Swift:

private func scaledImageFrom3x() -> UIImage {
        let locScale = UIScreen.mainScreen().scale
        let theRate: CGFloat = 1.0 / 3.0
        let oldSize = self.size
        let scaleWidth = CGFloat(oldSize.width) * theRate
        let scaleHeight = CGFloat(oldSize.height) * theRate
        var scaleRect = CGRectZero
        scaleRect.size.width = scaleWidth
        scaleRect.size.height = scaleHeight
        UIGraphicsBeginImageContextWithOptions(scaleRect.size, false, locScale)
        drawInRect(scaleRect)
        var newImage = UIImage()
        newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage
    }

OC:

- (UIImage *)scaledImageFrom3x
{
    float locScale = [UIScreen mainScreen].scale;
    
    float theRate = 2.0 / 3.0;
    UIImage *newImage = nil;
    
    CGSize oldSize = self.size;
    
    CGFloat scaledWidth = oldSize.width * theRate;
    CGFloat scaledHeight = oldSize.height * theRate;
    
    CGRect scaledRect = CGRectZero;
    scaledRect.size.width  = scaledWidth;
    scaledRect.size.height = scaledHeight;
    
    UIGraphicsBeginImageContextWithOptions(scaledRect.size, NO, locScale);
    
    [self drawInRect:scaledRect];
    
    newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    if(newImage == nil) {
        NSLog(@"could not scale image");
    }
    return newImage;
}

换肤功能

换肤功能,其实就是图片和颜色等资源的切换,也就是说你有几套皮肤,就提供对应的几套资源,当切换皮肤的时候,切换资源访问的路径并发出要换肤的通知,当前界面监听换肤的通知后再去刷新界面就完成了换肤的功能了。
我们实现一个ThemeManager的主题管理类,应用的所有资源访问都通过这个类来实现统一管理,所有的主题基本上都是由颜色和资源(图片,音频,文本等)来决定的,所以换肤时只要更改主题颜色库(themeColors)和主题资源库(themeBundle),实现如下:

Swift:

class CPThemeManager: NSObject {
    
    private var themeStyle: CPThemeType?
    private var themeBundle: NSBundle?
    private var themeColors: Dictionary<String, AnyObject>?

    // MARK: 单例
    static let shareInstance = CPThemeManager()
    private override init() {}
}

OC:

static BTThemeManager * _themeManager = nil;

@interface BTThemeManager () {
    NSDictionary *_themeColors;
}

@property (nonatomic, strong) NSDictionary *themeColors;
@property (nonatomic, strong) NSBundle     *themeBundle;

@end

@implementation BTThemeManager

@synthesize themeStyle = _themeStyle;
@synthesize themeColors = _themeColors;

+ (BTThemeManager *)getInstance
{
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        _themeManager = [[BTThemeManager alloc]init];
    });
    
    return _themeManager;
}

- (id) init
{
    if (self = [super init]) {}
    return self;
}

@end

上面也说了,换肤实质只是更换资源访问的路径,所以提供一个设置主题的方法来进行资源路径的设置(setThemeStyle),在重新设置完资源路径后,再对外发出更新界面的通知,实现如下:

Swift:

func setThemeStyle(themeStyle: CPThemeType) {
        
        //设置资源路径
        
        NSNotificationCenter.defaultCenter().postNotificationName("CPThemeChangeNotification", object: nil)
    }

OC:

- (void)setThemeStyle:(BTThemeType)themeStyle
{
    if (_themeStyle == themeStyle ) {
        return;
    }
    
    _themeStyle = themeStyle;
    
    //设置资源路径
    
    [[NSNotificationCenter defaultCenter] postNotificationName:BTThemeChangeNotification object:nil];
}

下面将说整个主题管理功能的最重要一步:监听主题的切换。

首先定义一个需要更新主题的协议方法:

Swift:

protocol CPThemeListenerProtocol {
    func CPThemeDidNeedUpdateStyle() -> Void
}

OC:

@protocol BTThemeListenerProtocol <NSObject>

- (void) BTThemeDidNeedUpdateStyle;

@end

然后在主题管理理里面添加一个注册监听主题切换方法:

Swift:

func addThemeListener(object: CPBaseViewController) {
    NSNotificationCenter.defaultCenter().addObserver(object,
                                                     selector:#selector(object.CPThemeDidNeedUpdateStyle),
                                                     name: "CPThemeChangeNotification",
                                                     object: nil)
}

func removeThemeListener(object: AnyObject) {
    NSNotificationCenter.defaultCenter().removeObserver(object)
}

因为Swift selector现在只能通过类名.方法名来设置,导致如果要使用则必须要继承一个基类,如果你们有更好的方法,求分享下。

OC:

- (void)addThemeListener:(id )obj
{
    if([obj respondsToSelector:@selector(BTThemeDidNeedUpdateStyle)]){
        [[NSNotificationCenter defaultCenter] addObserver:obj selector:@selector(BTThemeDidNeedUpdateStyle) name:BTThemeChangeNotification object:nil];
    }
    
}

- (void) removeThemeListener:(id)obj
{
    if (obj) {
        [[NSNotificationCenter defaultCenter] removeObserver:obj];
    }
}

最后在要实现主题切换的页面里添加主题管理类的监听切换方法,并实现协议的方法,把需要做主题切换的资源访问都放在这个方法里面,然后就搞定啦。搞了?好像少了点什么,还没有说如何去访问资源呢,这个我想大家都能自己去去实现,就是在基类里实现一个统一访问资源的方法:

- (void )BTThemeImage:(NSString *)imageName completionHandler:(void (^)(UIImage *image))handler;
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 耗时的操作
        NSString *imagePath = [NSString stringWithFormat:@"image/%@",imageName];
        UIImage *image = nil;
        
        //通过资源路径去访问
        
        if (image == nil) {
            image = [UIImage imageNamed:imageName];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            // 更新界面
            handler(image);
        });
    });
}

写在最后:欢迎大家一起多交流多学习,有更好的想法实现什么的, 求分享~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,583评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,669评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,684评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,682评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,533评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,396评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,814评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,458评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,745评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,789评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,565评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,410评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,828评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,050评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,342评论 1 253
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,793评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,010评论 2 337

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,957评论 4 60
  • 当前天数:第3天。 剩余天数:97天。 参与人:我。 路程:5圈。 地点:家里的小区跑步场。 开始时间:7点18分...
    Teratimes成长论阅读 182评论 0 0
  • 是啊!哥哥,我和你一样也想总有一天向这个世界展现自己的梦想,我们其实性格习惯都是一样的。哥哥,我想你了,今天再次听...
    怦然希米阅读 288评论 2 2
  • 一声巨响,将我弄醒。在这黎明来临时分,恐惧将我包围。我却无处藏身。
    爬行者糊糊阅读 153评论 0 0
  • 追逐着那一缕晨曦 我又守望在这里 淡淡的烟草味 是你 就是你 秋叶 已飘落 那是我淡淡的哀伤 夜不能寐的惆怅 你到...
    梦雪他乡阅读 320评论 3 17