iOS界面皮肤框架JJSkin简介

前言

JJSkin皮肤框架适用于所有iOS产品,如果你是一名iOS开发工程师,希望你读完本文,并且使用JJSkin在你的项目中。

首先定义一下这里皮肤的概念,皮肤即组成屏幕上界面的元素属性及其之间的关系。简单来说就是界面上控件的属性,像背景色,字体颜色等,以及这些控件之间的关系,例如,控件与控件之间的间距,控件相对于另一个控件的位置。做客户端一部分工作就是和皮肤有关,并且写皮肤相关的代码也是一件比较繁琐的事情。

问题

我们先来看一下JJSkin皮肤框架诞生的原因,也就是我们在做皮肤时遇见的问题。

1. 适配横屏和竖屏

2. 适配iPhone和iPad

3. 适配不同的屏幕尺寸

4. 适配特定机型,如6Plus

5. 需要对某一特定事件(如春节)做不同的皮肤

6. 如果不使用XIB或Storyboard,需要写许多代码对控件属性初始化


对于1,2,3,4点UE都需要做不同的设计,代码需要做许多逻辑判断,你可能会看到许多if/else语句,直接导致维护性和扩展性降低。如果说以上几点对于程序员想办法还是可以处理的,但遇到第5点,如果皮肤都是直接代码编写,那么程序员会哭死的。对于第6点,我一直在想有什么办法将初始化代码放在一个地方。

福音来了,JJSkin皮肤框架很好解决了以上问题,本文就是要介绍如何使用此框架。

框架代码请查看https://github.com/hamilyjing/JJSkin

JJSkin框架设计思想

我们将皮肤相关属性写在文件中,只修改皮肤文件,不修改代码,而达到换肤的目的。

皮肤可以由一组配置文件组成,都有对应iPhone或iPad的通用配置文件,用来定义通用属性,以及针对特殊情况的配置文件,例如针对320*480屏幕尺寸和6Plus等做特殊处理的配置文件。当用户想要获取某一属性(也有可能是一组属性)时,框架默认先从特定机型的配置文件查找(即5s,6,6p等),如果有的属性值为空,则针对当前屏幕尺寸的配置文件中继续查找(如果存在),然后将本次获取的属性和在上个配置文件获取的属性进行merge,merge的原则是只有当上个文件获取的属性值为空,则才将当前获取的value赋值给此属性,如果有属性值仍为空,继续查找通用配置文件,再次进行merge。

具体配置文件个数和读取顺序可以由用户决定,用户只需要编写配置文件类,实现JJSkinConfig协议,然后加入到JJSkinManager中即可。

在每个皮肤文件中,都会针对横屏和竖屏来对控件属性设值。获取属性值的原则如下:当界面是竖屏状态,首先读取竖屏属性值,没有读到,则选择下一个文件,继续读取竖屏属性值,直到读取到,即竖屏情况下,永远不会去读横屏属性值。反之,当界面是横屏状态,首先读取横屏属性值,没有读到,读取竖屏属性值,仍然没有读到,则读取下一个文件横屏属性值,然后竖屏属性值,直到读取最后一个文件的竖屏属性值(如果属性值仍然为空)。

如何使用

1. 下载JJSkin代码,将JJSkin/JJSkin文件夹放到项目中(ARC编译),导入头文件"JJSkin.h”。

2. 编写皮肤文件,根据实际需要来看是否要创建多个配置文件。

{

    "portrait": {

        "stringValue": "1234567890",

        "intergerValue": "890",

        "floatValue": "234.9",

        "boolValue": "1",

        "edgeInsets": "{1,2,3,4}",

        "rect": "{{5,6},{7,8}}", // CGRect值的格式{{x,y},{width,height}}

        "size": "{9,0}", // CGSize值格式{width,height}

        "color": "#FFc58dd0", // UIColor对应值格式#RGB或#ARGB或#RRGGBB或#AARRGGBB

        "textLabel": {

            "text": "portrait", // 必须和JJLabelStyle类text属性名字一直,下面会详细介绍

            "textAlignment": "right",

            "lineBreakMode": "clipping"

        }

    },

    "landscape": {

        "textLabel": {

        "text": "landscape",

        "textAlignment": "center",

        }

    }

}

3. 如果需要,编写配置文件类,实现JJSkinConfig协议,并加入到JJSkinManager对象中。


@protocol JJSkinConfig

- (NSBundle *)bundle;

- (NSString *)fileNamePrefix;

- (NSString *)fileType;

- (NSString *)landscapeJsonLabel;

- (NSString *)portraitJsonLabel;

- (NSString *)iPhoneFileNameSuffix;

- (NSString *)iPadFileNameSuffix;

- (NSArray *)fileNames;

@end

皮肤文件默认放在当前bundle里,并且文件名前缀是"jjSkin-",文件类型是"json",例如通用皮肤文件名"jjSkin-iPhone.json",375x667尺寸皮肤文件名"jjSkin-iPhone375x667.json",针对iPhone 5C的皮肤文件名"jjSkin-iPhone 5C.json"。其中fileNames决定框架读取皮肤文件的顺序。

4. 直接获取或更新控件属性值

获取和更新控件属性值,都是通过ID得到,ID是配置文件标签的路径,以点号分割,如@"R.floatValue",@"R.textLabel.text"等,ID以"R"开头,并且不包含横屏和竖屏标签,框架会根据界面方向自动加入。

NSString *stringValue = [JJSkinManager getStringByID:@"R.stringValue"]; // "1234567890"

CGFloat floatValue = [JJSkinManager getFloatByID:@"R.floatValue"]; // 234.9

CGRect rect = [JJSkinManager getRectByID:@"R.rect"]; // {{5,6},{7,8}}

/** 

    直接获取UILabel对象。

    竖屏下,label内容是"portrait",并且文字右对齐。

    横屏下,label内容是"landscape",并且文字居中对齐。

*/

//神奇的地方,只需要一行代码,label的属性在不同的情况下如iPhone,iPad,横屏,竖屏等等,有不同的值。

UILabel *label = [JJSkinManager getLabelByID:@"R.textLabel"]; 

/** 

    更新已存在UILabel对象属性。

    label1和label的text和textAlignment属性值相同。

*/

UILabel *label1 = [[UILabel alloc] init];

[JJSkinManager updateLabel:label1 withID:@"R.textLabel"];

皮肤Style

皮肤Style定义的是控件属性的集合。皮肤配置文件中每个key都对应一种Style。当key对应的value是string类型,则它是JJCommonStyle,例如"R.stringValue";如果value是dictionary类型,则它是一种属性集合style,如JJLabelStyle,JJButtonStyle等,以及用户自定义的style,像"R.textLabel"是JJLabelStyle。一种style由一个或多个style组成。

当用户获取某一ID对应的值时,JJSkin将文件中ID对应的value转化成某一种Style(由用户决定,即调用JJSkinManager不同的API),然后由Style实现对象的生成。

皮肤Style有一个公共基类JJSkinStyle,框架默认提供常用的Style,如JJLabelStyle,JJImageStyle等,用户可以定义自己的Style,需要继承JJSkinStyle,并实现如下两个方法:

+ (id)objectFromStyle:(id)style;

- (void)updateObject:(id)object;

然后使用JJSkinManager下面两个API获取或更新控件:

+ (id)getObjectByID:(NSString *)id withStyleClass:(Class)styleClass;

+ (void)updateObject:(id)object withID:(NSString *)id withStyleClass:(Class)styleClass;

当配置文件属性使用JJCommonStyle类型,此类型的key可以是任何名字。

"portrait": {

        "stringValue": "1234567890”, // “stringValue”可以是任意名字

        "intergerValue": "890",

        "floatValue": "234.9",

        "boolValue": "1",

        "edgeInsets": "{1,2,3,4}",

        "rect": "{{5,6},{7,8}}", // CGRect值的格式{{x,y},{width,height}}

        "size": "{9,0}", // CGSize值格式{width,height}

        "color": "#FFc58dd0", // UIColor对应值格式#RGB或#ARGB或#RRGGBB或#AARRGGBB

    }

当配置文件属性使用非JJCommonStyle类型,此类型的key必须和对应Style类属性名字一致。因为框架通过运行时方法,匹配类属性名字和key,然后进行赋值。

"landscape": { 

        "textLabel": {// textLabel是JJLabelStyle类型

        "text": "landscape", // "text"必须和JJLabelStyle中text属性名字一直,否则赋值失败

        "textAlignment": "center",

        }

    }

皮肤文件中有一个status的key,当值为"finish"时,即便有部分属性值没有值,也不在继续继续查找,但不包括子Style;当值为"finishIncludeSon"时,其子Style的属性值没取完整,也不再继续查找。

"landscape": {

        "textLabel": { // textLabel的属性只有text和textAlignment,因为没有子style,所以不会查找portrait和其他配置文件。

        "status": "finish",

        "text": "landscape",

        "textAlignment": "center",

        }

    }

综述,我们只需将皮肤属性写在配置文件里,只需一行代码就可以适应不同状态下属性值的变化。

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,967评论 3 119
  • 我只知道时间不够用,只知道最近从从亚马逊、当当买的书在床头已经堆积半年还没有看完。 另外很多想做的事也没去做,比如...
    穆清_f275阅读 345评论 0 0
  • Hello,晚上好~我是你们的小渡 高考结束了,宝宝们终于可以自由的happy啦~今天刚好周末,和你们一起休息一天...
    南方有自渡阅读 73评论 0 0
  • 原以为我们由相识可以走得更远,却没想只是错误的时间遇到了不对的人,聚散总无缘,来不及相知便各自走远~ ...
    傻丫头的西瓜阅读 76评论 0 1
  • 本文参加【建筑圈春节活动 | 我想欣赏你家乡/旅途的建筑可以吗?】活动 这两年去了国内国外好多景点玩,手机存了一万...
    四夕殇阅读 2,548评论 44 53