对iOS中Storyboard的控件进行功能性扩展方案
一、前言:
Storyboard能够让我们对UI界面进行快速的搭建,并利用AutoLayout进行屏幕的适配,它能极大的节省我们的开发时间,同时以便我们能够在代码中完全专注于核心功能的实现上。
但是,对于有多语言需求的项目工程,Storyboard的一般做法是:
1、对新增的国际化语言配置文件中进行重新的翻译处理。
2、把Storyboard中的控件拉到代码中去重新赋值。
这两种实现方式,在处理起来都让人感到比较恶心。当然也有人说可以通过脚本来进行处理,但实现的效果可能并不是那么理想。
在这里,我们提出了另一种解决方案,就是通过编写对应控件的Category,让其在Storyboard上进行功能性扩展。
二、IBInspectable关键字:
在此之前,我们要先介绍一个关键字——IBInspectable。
在NIB、XIB、Storyboard的identifier inspector中,我们可以在User Defined Runtime Attributes对选中的控件进行key-value coded,虽然功能强大,但一个属性的关键字路径,类型和属性值需要在每个实例设置,没有任何自动完成或输入提示,这使得工作很繁琐。而 IBInspectable 属性彻底的解决了这个问题:在 Xcode 6,你现在可以指定任何属性作为可检查项并为你的自定义类建立了一个用户界面。
比如:当我们用IBInspectable去修饰一个控件的某个属性时,Storyboard对应控件的Attributes inspector中将会多出该属性的选项。当我们对该选项进行修改赋值时,identifier inspector中的key-value coded就会相应的做出变化。
三、对Storyboard的控件进行多语言功能扩展:
这里,我们拿UILabel作为示例,当然,UIButton和UITextField等的文本多语言设计思路也是一样的:
- 1、通过Category对UILabel添加一个BOOL值的属性扩展。
- 2、用IBInspectable关键字对这个属性进行修饰,使得我们能够在Storyboard中对其进行修改。
- 3、当我们在Storyboard中对该属性设为true时,我们希望UILabel的text能够作为多语言实现方法NSLocalizedString(key, comment)中的key,从而对UILabel的text重新赋值。
这样一来,我们只需要在Localizable.strings文件中对文案进行键值对编写便可实现本地化,无需再把控件拖进代码中去修改了,也不用新增国际化语言配置文件中去重新翻译了。具体实现代码如下:
UILabel+SBLocalizable.h中:
#import <UIKit/UIKit.h>
@interface UILabel (SBLocalizable)
/**
是否把当前text作为多语言的key
*/
@property (nonatomic, assign) IBInspectable BOOL textAsKey;
@end
UILabel+SBLocalizable.m中:
#import "UILabel+SBLocalizable.h"
#import <objc/runtime.h>
static const void *kLabelTextKey = &kLabelTextKey;
@implementation UILabel (SBLocalizable)
- (BOOL)textAsKey {
return [objc_getAssociatedObject(self, @selector(textAsKey)) boolValue];
}
- (void)setTextAsKey:(BOOL)textAsKey {
objc_setAssociatedObject(self,
@selector(textAsKey),
@(textAsKey),
OBJC_ASSOCIATION_ASSIGN);
if (textAsKey) {
objc_setAssociatedObject(self,
kLabelTextKey,
self.text,
OBJC_ASSOCIATION_COPY_NONATOMIC);
self.text = NSLocalizedString(self.text, nil);
} else {
self.text = objc_getAssociatedObject(self, kLabelTextKey);
}
}
@end
四、对Storyboard的控件进行颜色功能扩展:
看了上面对多语言功能的扩展,我们可以用类型的方法,对控件添加一个UIColor的属性,并用IBInspectable关键字对其进行修饰,这样便可在Storyboard上对该属性的颜色进行修改了。
但是,一旦设计师要求对颜色进行调整,那么,我们便要一个个的去把相应的控件找出来,重新给颜色赋值,这又是一个恶心的任务。于是我们提供了另一种设计思路,这里用UIView作为示例:
- 1、新建一个UIColor的Category,并用类方法返回我们需要的颜色。
- 2、通过Category对UIVIew添加一个字符串属性扩展,这里我们就命名为bgColorName吧。
- 3、用IBInspectable关键字对这个属性进行修饰,使得我们能够在Storyboard中对其进行修改。
- 4、在Storyboard中UIView控件的新属性上,我们填写刚才新建的UIColor的取得颜色的类方法名。
- 5、在bgColorName的set方法中,我们把取得的字符串转为UIColor的类方法,并取得对应的颜色,从而给UIView的backgroundColor重新赋值。
这样一来,一旦我们要替换某个颜色时,只要进行全局搜索,并让Storyboard以Source code的方式打开,便可以进行全局替换。具体实现代码如下:
UIColor+CorosColor.h中:
#import <UIKit/UIKit.h>
@interface UIColor (CorosColor)
+ (UIColor *)clearClr;
+ (UIColor *)whiteClr;
+ (UIColor *)blackClr;
+ (UIColor *)blueClr;
+ (UIColor *)redClr;
@end
UIView+BackgroundColor.h中:
#import <UIKit/UIKit.h>
@interface UIView (BackgroundColor)
/**
背景颜色名称
*/
@property (nullable, nonatomic, strong) IBInspectable NSString *bgColorName;
@end
UIView+BackgroundColor.m中:
#import "UIView+BackgroundColor.h"
#import <objc/runtime.h>
@implementation UIView (BackgroundColor)
- (nullable NSString *)bgColorName {
return objc_getAssociatedObject(self, @selector(bgColorName));
}
- (void)setBgColorName:(NSString *)bgColorName {
if (!bgColorName || bgColorName.length == 0) {
return;
}
UIColor *color;
SEL sel = NSSelectorFromString(bgColorName);
if ([UIColor respondsToSelector:sel]) {
color = [UIColor performSelector:sel];
}
if (!color) {
return;
}
self.backgroundColor = color;
objc_setAssociatedObject(self,
@selector(bgColorName),
bgColorName,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
五、结束语:
通过以上对IBInspectable的使用和设计思路,我们就可以根据需要,对相应的控件在Storyboard中进行功能性扩展,从而更好的利用Storyboard在UI搭建上的便利性,使得我们能在更简洁的代码中去专注于核心功能的实现上(PS:大家有空可以看看另一个相当有意思的关键字——IB_DESIGNABLE)。
最后需要注意的地方是,当我们不想使用该Category扩展出来的功能时,仅仅是删去Category代码文件是不够,因为当你在Storyboard中对相应控件的新增属性进行了更改赋值后,identifier inspector中的key-value coded并不会自动去删除对应的属性,这里我们还需要手动去删除对应的编码值。或者我们在删除Category代码文件前,先将Storyboard中控件对应的属性改回默认值Default,然后再删除Category代码文件。