引言
学到这里,xib给我带来的帮助已经很大了,最大的莫过于UI控件的创建、属性的赋值再也不用写代码,就UI开发来说大大减少了代码,而且设置delegate、addTarget、属性声明等也不用自己写代码了。SB的segue使我们的页面跳转也不需要写代码了,还有强大的cell功能更是在开发中起到了很大的作用,除此之外,xib还有一些高冷功能,这些功能有些很少有人使用,你在网上也几乎看不到关于它的文章,但是它们确实是很好用,下面我们就来一起看看。
是时候总结一下了
当我们在代码中打出IB的时候,代码提示会是这样的:
在开始介绍一些高冷用法之前,我们先总结一下它们:
IBAction与IBOutlet
这是我们最常接触的两个,大家对它们已经有了很好的认识,这里只简单的说一下。
对于一个类来说,方法和属性(在这里属性与字段合在一起表示一个概念)是最重要的两个要素,而IBAction与IBOutlet就是分别标识方法与属性的,它们标识着由它们修饰的方法和属性是来自xib的,我猜它们是给编译器看的。
IBInspectable
在OC中使用IBInspectable,在swift中使用@IBInspectable
它是xcode6引入的新功能,它修饰的属性或者实例变量,会显示在xib中的属性栏中(Show the Attributes inspector),我们之前讲的东西都是xib是如何影响代码的,而IBInspectable是可以用代码影响xib的,可能我的表述不是很正确,还是看一个具体例子吧。
@interface ViewController : UIViewController
//gj_testFlag用IBInspectable修饰后,就能在xib中看到这个属性了,当然也可以用xib进行赋值了
@property (assign, nonatomic) IBInspectable BOOL gj_testFlag;
@end
这个例子这是为了说明IBInspectable的用法,具体应用没有太大意义,但是如果我们会灵活运用IBInspectable就会带来极大的方便,其实xib的好多用法都有着意想不到的效果,大家可以在各个情况下去尝试,这是一件很酷的事。
IB_DESIGNABLE
在OC中将IB_DESIGNABLE写在@implementation前,在swift中将@IBDesignable写在class前
它也是xcode6引入的新功能,它的作用是可以在不运行的情况下把你的代码显示在xib或SB文件中。
两点说明:
1.这是一个针对UI显示的功能,所以只能是在UIView及其子类或者NSView及其子类上生效。
2.要想使IBDesignable起作用必须把代码写在drawRect里才能显示,同样的代码,我写在了awakeFromNib里就不会再xib中看出效果,只有写在了drawRect才可以。
举个例子:
我们建一个工程,新建一个TestView类继承自UIVIew,在Main.storyboard里拖一个View,class设置为TestView,背景设置成灰色。
然后写代码:
IB_DESIGNABLE
@implementation TestView
- (void)drawRect:(CGRect)rect {
UIBezierPath *firtPath =
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 10, 180, 180)];
CAShapeLayer *shapeL = [CAShapeLayer layer];
shapeL.lineWidth = 20;
shapeL.path =firtPath.CGPath;
shapeL.strokeStart = 0;
shapeL.strokeEnd = 1;
shapeL.strokeColor = [UIColor yellowColor].CGColor;
shapeL.fillColor = [UIColor clearColor].CGColor;
[self.layer addSublayer:shapeL];
self.layer.cornerRadius = 30;
self.layer.masksToBounds = YES;
}
@end
此时再看Main.storyboard中View的变化:
我们的IB_DESIGNABLE功能已经生效了
我们把下面的代码从drawRect:中剪切到awakeFromNib函数中会发现,圆角不见了,这里要特别注意。
self.layer.cornerRadius = 30;
self.layer.masksToBounds = YES;
IBOutletCollection(ClassName):
将基于IBOutlet创建的对象放在一个NSarray里。
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *testViewArr;
创建了一个array,里面放的是用IBOutlet创建的UIView.
注意最好用strong进行修饰,而且如果你声明的不是NSArray,即便是UIColor,系统也不会报错,你打印这个color发现,系统用的还是NSArray。这个array的顺序是连线时候的顺序,但是不排除不同版本的xcode会改变这个顺序,所以最好不要依赖这个顺序。
也可以像拖IBOutlet那样创建:
用xib给控件添加手势
Files Owner的应用举例
有这样一个场景,VC中有一个textfield要设置inputAccessoryView属性,该属性的view显示起来很复杂,有多个按钮,每个按钮对应不同的事件。
一般的做法是用代码写一个这样的view赋值给inputAccessoryView属性,其实这个例子可以用xib实现的更优雅,不用写代码就可以完成(当然点击每个按钮后的事件处理代码是要自己写的)。
例子中要考虑的重点是:
如果创建了一个AccessoryView.xib去拖出这样一个view,虽然不用“画”UI了,但是我们要建一个AccessoryView.h、AccessoryView.m类去与xib文件对应,在AccessoryView.m中把它上面的按钮事件记录下来,一旦触发事件,要通过delegate或通知等其他形式把事件从AccessoryView类传递给VC类,这样使事情更加的麻烦了,如何解决?
有人会想:创建AccessoryView.h、AccessoryView.m是没有必要的,因为他们除了传递事件,根本没做任何事情,这样的话就不创建他们,只有AccessoryView.xib文件,然后把xib中的按钮分别拖动到VC类中建立起IBAction的“连线”关系,事情就搞定了。
这个思路很好,但是我们会发现,并不能实现AccessoryView.xib与VC中的“连线”,因为VC类根本不认识这个xib,因此该VC是不允许这个xib通过“连线”向它内部添加代码的,如何解决这个问题?——Files Owner!
将AccessoryView.xib的Files Owner指定成该VC的类,此时再拖“连线”到VC就可以了,这样xib中按钮的事件就能直接回调到VC中我们设置的方法里了。
这是解决这个问题最简单的方法,不用写一行代码,他给我们的启示是:
xib文件是可以不依托于UIView子类、UIViewController子类单独使用的,只是这种情况比较少见,这是一个例子。
由此我们应该放开我们的思想束缚,去大胆的实践xib的各种用法。
这里再说一点:之前讲过基于UIView子类的xib的使用方法:
TestView *tView = [[NSBundle mainBundle] loadNibNamed:@"TestView" owner:self options:nil][0];
这个方法就是从bundle中找到xib文件(其实是找到被xcode编译好的nib文件)将其加载到内存中,与该xib文件是否与其他类建立了关联无关,所以这里加载xib文件也是用的此方法。
这个例子稍稍变一下需求,就是我们在一天一点xib:4简单使用xib里谈论Files Owner要说的例子了,如果点击textField的AccessoryView会有UI上的变化,或者交互的话,最好就是要创建AccessoryView.h、AccessoryView.m,然后把AccessoryView.xib分别与AccessoryView.m和VC的.m连线,UI的处理在AccessoryView.m中完成,逻辑的处理在VC的.m中完成,这样的“双连线”很好的解决了要把事件从AccessoryView.m传递给VC的.m的问题。
xib的国际化
有些app是要做国际化适配的,下面来说说xib如何做国际化,相当的简单。
1.给project添加多语言,我们以法语为例。
2.选择要国际化的xib或SB文件。
3.此时支持国际化的xib或SB文件就变成了这样:
4.下面来说说国际化中文件的组织问题:
.lproj是管理多语言相关的文件,默认工程会创建Base.lproj,里卖放xib或SB文件,我们可以在其他路径创建xib或SB文件,但是如果要使得这个文件支持国际化,系统就会把它放在Base.lproj里。我们多支持一种语言就会多创建一个.lproj文件,例如fr.lproj用于支持法语,除了Base.lproj外,其他的.lproj文件中是不放xib或SB文件的,他们会放.strings文件,.strings文件是国际化对应的重要文件,不同的语言,放在不同的.strings文件中。系统根据iOS设备当前的语言会去找到当前的.strings文件并加载里面的string,如果大家对国际化比较陌生,可以先在网上找找国际化的一些东西来看,这里默认大家对国际化是了解的。
5.开始国际化,向SB的VC中拖入一个label,显示的文字为:“你好”。拷贝object id。
6.打开xib或SB文件对应的一种语言的.strings文件(我们例子中是Main.strings(French)), 添加:
rii-xB-u6s.text = @"bonjour"
bonjour是法语中你好的意思。
7.把iOS设备的语言设置为法语,运行程序,大功告成!
两点说明
1.网上普遍流行的xib国际化的方法是用终端执行命令的方式,我个人认为,上面是最简单的方法,xib真的很强大。
2.国际化后就一定要建立一种相对应的本地化的语言,不然在切换语言的时候app还会显示之前的那种语言,这一点很重要,比如你app支持的本地语言是汉语,还想支持法语,那么一旦你国际化了,你其实要在xcode中添加两种语言,汉语和法语,生成两个.strings文件,一定切记。
User Define Runtime Attribute
之前在介绍xib右边栏的时候简单的说了一下,这是xib很高冷的一个用法,但实际上很简单,很方便,希望大家学会以后,多多使用,因为真的是太方便了。现在来具体说明。
1.拖一个view到SB的View上,为了看出效果,我这里给了它一个灰色背景。
2.设置User Define Runtime Attribute。
3.运行看效果:
是的,别怀疑,就是这么简单,把一些xib中不能设置的属性,写在这里,就可以了,弄个圆角矩形的button再也不用写代码了,方便吧。
Object
这是一个更高冷的xib用法,相对来说比较复杂,我们一点点的开始。
1.我们先建立一个Person类,继承自NSObject。
2.我们在Person类中添加一个简单的测试方法:
- (IBAction)sayHello:(id)sender {
NSLog(@"hello person");
}
你可能会有疑问,一个继承自NSObject的类,是不能使用xib或SB的,为什么写个方法还用IBAction?而且编译器还不报错?
这里我想说:我们学习的过程其实是对现有知识、经验的不断地修正,其实科学的发展也这样。xib是可以和继承自NSObject的类发生关系的,我们往下看。
3.我们在一个VC中添加这样的代码:
#import "Person.h"
@interface ViewController : UIViewController
@property (nonatomic, strong) IBOutlet Person *aPerson;
@end
你可能同样有疑问,Person类为什么可以加IBOutlet修饰,控件栏里也没有Person类啊,为什么编译不报错?我们还是往下看。
4.我们找到ViewController对应的xib或SB文件,拖入一个button,把这个button与Person类中的sayHello:函数“连线”,你发现根本连接不上,其实也很简单,两者一点关系都没有,都不认识,怎么“连线”呢?那么我们如何让他们彼此认识?往下看。
5.在右边栏中找到Object这个对象,拖动它到左侧边栏中,把Object对象的class设置为Person,大功告成!
6.最后一步,连线,建立关系,运行测试。
这里注意,最重要的一步是要把Object对象的class设置为Person,其实xib使用中最重要地方就在class的设置,和file's Owner的设置,这是使用xib的核心。
有些人会说,这又什么卵用?
我个人认为,这种用法其实意义很大:
1.你如果遇到这种Controller需要其他类处理的情况,xib也是可以应用的,而且还是不用写什么代码,设置什么delegate,相当的方便,如果你不知道如何应用,只能说明,你之前没有想到过xib会如此的强大,可以做如此多的事情。
2.我们都熟悉Category,知道他的一个优点就是把不同的实现可以分散到不同文件中,或者把相同的功能放在一个Category中管理,更清晰,就像苹果自身的代码那样,你跳进UIViewController中看看,虽然是一个文件,但是不同功能的实现基本上都是在不同的Category中的,这给我们一个思路,如果一个VC交互复杂、UI上又很多button有很多跳转,我们完全可以用这种方式把耦合性地的部分抽离出来,或把相同功能或业务的部分抽离出来,独立封装成类,这样就可以避免几千行代码的超级VC类的出现,当大家在争着解决臃肿VC的问题而提出的各种五花八门的设计模式的时候,我想这也是一个不错的解决方案(以后会对MVC、MVP、MVVM、MVCS等各种模式提出一些自己的看法)。说到解决方案,如果有看过《Objective-C编程之道——iOS设计模式解析》这本书的朋友会知道,里面为了解决页面跳转混乱、交互复杂的情况引入了中介者模式,个人认为,充当终结者最好是一个NSObject类的子类,而xib的这种用法是中介者模式的一个很好的应用。
总结
xib给了我很大的便利,学习的过程中也给了我很多惊喜,这些高冷用法都是我在学习过程中不断总结出来的,希望大家学习完了之后和我一样,即提高了开发效率,同时学习的过程也令你愉悦。
欢迎大家和我交流沟通,若文章中有错误和纰漏,恳请指正,谢谢。