xib的一些感悟

引言

这两天突然想起之前看到过有的项目中用到了object, 好奇心起,就顺便回溯下xib的使用吧

xib优缺点分析

1.有一定的学习成本

2.没有代码表达清晰

3.出错不易发现,无法调试,尤其是“连线”出了问题

4.文件易冲突,且难解决,不利于团队合作,尤其是在团队中用SB

5.执行效率没有代码高

6.有时不利于封装

7.灵活性不强,尤其界面灵活多变, 高度不定

8.使用富文本卡顿现象明显

优点

1.开发效率高

2.减少大量胶水代码

3.通过xib可以快速、高效的学习控件

4.适配性明显优于代码(auto layout、size classes)

5.可视化

6.在一定程度上修改方便

VC View 使用xib

1 创建方法

VC在创建的时候可以之间勾选创建 xib


image

View不能够直接勾选创建,需要command+N选择View创建一个xib,一般来说,我们需要创建一个相对应的类, 在右边栏第三个选项(show the identity inspector)下面的custom class-> class中填写你要与该xib绑定的UIView子类的名字

2 初始化方法

VC加载xib的方法
我们的父类在初始化的时候去自动帮我们找与之对应的xib文件,那么问题来了,父类怎么知道我有没有xib文件呢?是这样,父类会判断有没有和我们这个要初始化的VC相同名字的xib文件,如果有就会加载该xib文件,如果没有,父类就认为我们该VC没有xib文件,就会走正常的init方法。

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;

如果一旦我们的VC类的名字与对应的xib文件名字不同的时候,我们就必须调用这个初始化方法来创建VC实例了;

[[ViewController alloc] initWithNibName:@"xxx" bundle:[NSBundle mainBundle]]

xxx填写xib文件的名字, 名字不同也是可以的

2 view加载xib

TestView *tView = [[NSBundle mainBundle] loadNibNamed:@"TestView" owner:self options:nil].firstObject;

上述代码再次说明xib文件是资源文件,放在main bundle中,@"TestView"是xib文件的名称,后面两个参数暂时不用了解,就固定传self和nil就行,值得说的是,loadNibNamed: owner: options方法返回的是一个数组,而不直接是对象,这是考虑到了Mac开发会有多个对象返回的情况,在iOS开发中就只有一个,固定取第一个就好。

注:一般的UIView对象,代码初始化的时候都会调用initWithFrame:方法,但是用xib创建的UIView对象是不会调用此方法的,因为该对象的Frame在xib文件中就可以确定了。以xib的形式保存控件对象的过程其实叫做固化(archive),通过xib文件创建控件的过程叫做解固(unarchive),固化是iOS持久化的一种比较好的解决方案,以后有机会会说说iOS持久化的各种方式的优劣,这里不再深入,而与固化相关的初始化函数是:

- (instancetype)initWithCoder:(NSCoder *)aDecoder

所以,当以xib创建UIView对象的时候这个函数会调用,之前在initWithFrame:中要做的事情,可以放在initWithCoder:中,或者放在:

- (void)awakeFromNib
{
    [super awakeFromNib];
    //...
}

SB文件的使用

由于SB文件与VC一般是一对多的关系,所以我们不仅要知道即将创建的这个VC的实例对象是加载的哪个SB,而且还要知道加载的是该SB中的哪个具体的VC

SecVC *secVC = [[UIStoryboard storyboardWithName:@"Demo" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"SecVC"];

有的项目有多个sb进行跳转, 一般来说需要找到那个sb的初始入口那个VC

SecVC *secVC = [[UIStoryboard storyboardWithName:@"Demo" bundle:[NSBundle mainBundle]] instantiateInitialViewController];

冲突解决

xib、SB文件有两种查看方式,当文件冲突的时候普通的interface builder方式打开文件会失败。

此时右键该xib或SB文件,选择source code方式打开文件,之后解决冲突的思路与解决project.pbxproj文件冲突的方式是一样的,文件中全局搜索<<<<或>>>>定位到错误位置,根据实际情况,删除一个版本的代码,保留另一个版本的代码,直到文件中所有冲突都解决掉。

File Owner

Files Owner指这个xib文件的所属文件是谁,简单的说是xib文件和谁建立起交互,用户通与该xib呈现的页面进行交互的时候,谁来处理背后的逻辑。具体来讲xib文件能拖动“连线”到哪个源文件中去建立IBAction、IBOutlet、delegate、datasource等。

一般基于View创建的xib的Files Owner都指定为一个VC(但一般应用都会创建一个相对应的类,然后指向该类)。基于VC创建的xib,创建的时候系统就已经把该xib文件的Files Owner指向了该VC,一般这种情况就不对Files Owner做修改了。

1490498-5f79953535024ab1.jpg

IBAction

建立IBAction连线的方法:

1.选中需要连线的对象,按住control键,拖动该控件到Files Owner类的@implementatio中松手,填写方法名即可。
建立IBAction连线的方法:

1.选中需要连线的对象,按住control键,拖动该控件到Files Owner类的@implementatio中松手,填写方法名即可。


1490498-56ce8578de3b63d8.gif

2.先在@implementatio中定义一个方法,在返回值中写IBAction,然后点击前面的空心圆,拖动到xib的对象上,两者就建立了“连线”的关系。
1490498-fe72df4448bcd619.gif

IBOutlet

也是“连线”的一种,用于标记属性或变量,此方法将Files Owner中的属性或字段,与xib中的某个对象通过“连线”建立起关系。

IBOutlet建立“连线”与查看“连线”的方法与IBAction相同。但要注意的是:如果拖动“线”到@interface里,就生成属性,如果拖动到@interface{}里或者拖动“线”到@implementatio中的{}里就生成变量。

这里简单说一下:在@implementation中{}里写变量(一般不这样干)和在@interface XXClass()(匿名Category)里的{}中写变量是一样的,都是匿名的。

不怎么用却实用的

基于View创建的xib,是可以“连线”到自己View所在类中的,如果给该xib设置了Files Owner的属性后,可以同时“连线”到Files Owner的类中。

我们可能会遇到这种情况:要封装一个View为一个单独的类,该View是可交互的,点击后,要发生变化,同时又要把交互的事件传递给VC,如果用代码的话,就要把事件从View类传递给VC类,而如果你封装的类用了xib,那么事情就简单多了,同时“连线”到View类和自己Files Owner类对应的VC中,在View类中处理UI的变化,在VC中处理逻辑,而不需要任何事件的传递,当用户交互的时候,这两根“连线”都会被回调。

SB使用

xib可以基于View、VC甚至自己独立的使用,而SB只能基于VC使用,为什么说比xib更加强大呢?主要是下面的两个原因:

1.SB支持segue

2.SB对cell的支持更加强大

1490498-c779b4a50255c30f.gif

)]

我们在一个VC中选择要发生跳转的按钮,按control拖动到另一个VC上就会出现一个菜单,在菜单上你就可以选择跳转的方式push、present,这样不用写一行代码就能完成页面间的跳转,而两个VC之间的像纽扣一样用线连着两个VC的的东西就是segue,是一个UIStoryboardSegue对象,我们可以简单的理解成是完成页面跳转相关功能的一个类,是不是很简单?

segue虽然简单,如何传参?

我们知道用SB的segue来实现也页面跳转十分方便,但是如果要向跳转的页面传参该怎么办?

假设我们要点击ViewController这个VC里的一个按钮,跳转到SecVC这个VC中,把testTitle这个参数传过去。第一步还是要选中按钮,拖到SecVC里,然后选中这个segue,给它一个id值,确保用代码可以找到它,然后在ViewController中编码:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"sec"]) {
        SecVC *secVC = (SecVC *)segue.destinationViewController;
        secVC.testTite = @"hello world";
    }
}

设置SB中segue的identifier,主要就是要在这里使用,因为多个button发生的多个页面跳转都是回调这个函数,所以要用identifier来区分是哪个segue,跳哪个页面,传哪些参数。

id的sender就是触发跳转的那个button或其他控件。
1490498-f26590ab61a5b9bf.gif

不确定的跳转如何使用segue?

一下场景经常出现:甲VC中有个button,点击后有可能跳转到乙页面,也可能跳转的丙页面,怎么解?

我们假设我们要点击ViewController这个VC里的一个按钮,有可能要跳到SecVC,有可能要跳thirdVC。
1.在SB中准备segue。


1490498-a890aa691ffb90b7.gif

注意我们这次创建的segue的方式并不是选中button按control拖到另一个VC中,而是选中VC,右键选中triggered segues里manual后面的+拖动到另一个VC里,这个很重要,而且还是要给segue一个id。

更灵活的segue

如果你熟练掌握了segue他其实可以做很多事情,其实segue并不仅仅能完成跳转,还有一类segue叫做relationship segue,之前是帮助大家理解,所以说的比较简单,其实我们选中一个NavigationController按control键拖动到另一个VC上的话也可以生成segue,我们选中root view controller,就将Nav的rootViewController属性设置成了这个VC,这个segue就是relationship segue,同样,拖动一个tabbarController也会出现viewControllers的relationship segue。

更好的使用Cell

用xib的时候,要想用cell,通常是建立一个cell子类,一个与之对应的.xib文件。用SB的时候要想用cell就简单的多了,因为cell是可以直接拖动到tableView里的,直接在tableView里管理cell设置属性,当然如果是复杂的cell还是要子类话对应的.h、.m的。

SB在使用cell分两种情况:1静态cell,2动态cell。

静态cell
1490498-240e6c7c16d2536a.jpg

注意静态cell一定要基于UITableViewController,否则会报错。
展示静态cell的tableView是不用调用自己必须实现的datasource协议的

你会很惊讶,为什么必须调用的datasource协议都不用实现?答案是静态,静态的cell,意义就在于你在xib中设置成什么样,他就展现什么样,不会再调用datasource向你要cell了,因为在SB文件中已经确定下来了。

在做一些“死”页面的时候SB的静态cell是很好的选择,静态cell也不是什么都不能做,静态cell里的button还是可以拖到@implementation中形成IBAction的,但是是无法生成IBOutlet属性或字段的。

即使你强行的给一个静态的cell指定了一个cell的类,也是无法向其内部拖入IBOutlet的。这就是静态cell的局限性,但是如果你要设置的数据不多还是可以考虑用静态cell,因为你可以通过给cell上的控件设tag来找到它从而赋值。

动态cell的使用

在上图中设置content属性为Dynamic Prototypes就可以了。使用动态cell的话就要在VC中实现那两个必须实现的datasource协议了,下面举个简单的例子:
1.给cell指定一个class
2.给cell设置id,重用机制使用,其他属性就不详细说明了,和其他控件的属性差不多。

添加tableHeaderView和tableFooterView

是的,没有听错,不用代码,“拖”出header于footer,其实很简单,选中tablView,在控件中找到View拖到tableView上,往最上方拖动,知道看到左右有两个圈的时候松手,这个View就是tableHeaderView了,同理,往最下方拖,就是tableFooterView。


1490498-cd74b79914fce993.gif

LaunchScreen.storyboard

从iOS8开始iPhone多了4.7"和5.5"的两种设备,这使得适配更加复杂,特别是设置启动图,如果考虑到横竖屏的话,要做好多张图,最重要的是,启动图是最占体积的东西,为了更好、更方面的配置启动图,LaunchScreen.storyboard出现了,简单来说,启动的时候会加载这个SB文件,我们可以同过它更方便的设置启动图,可以用auto layout减少启动图数量的使用,但此功能只支持iOS8及其以上的系统。
那么问题来了,我要想适配更低的系统怎么办?答:不用。
如何禁止该功能?

1490498-f548fcc2e8376e63.jpg

Storyboard Entry Point

如果我们用xcode6或者更高版本的xcode创建工程的话,你会发现自动就有了一个Main.storyboard

application:didFinishLaunchingWithOptions:中没有一行代码运行就没有问题,并不像之前那样,要创建window,指定rootViewController,这些是如何实现的?

1490498-b860cb27ad9a3659.jpg

xcode自动配置了一个SB文件,而以上的这一切都有xcode自动帮我们完成了。

那么问题来了:一个SB是可以对应多个VC的,他选哪个VC作为window的rootViewController?答案是Storyboard Entry Point,这个东西就是用来指定那个作为rootViewController的,也就是说,xcode会找到表示为Storyboard Entry Point的那个VC加载它成为rootViewController,而以后的跳转就由我们之前介绍的方式:


1490498-f108e0c05c1f70b0.jpg

勾选就是设置了Storyboard Entry Point,设置了Storyboard Entry Point的VC会有一个向右的箭头指向它,注意你在Main Interface里选的SB文件中一定要有VC勾选了这个,不然xcode是不知道如何设置rootViewController的,你不用担心多选的问题,你如果选择一个新的VC,旧的那个VC就自然没有了Storyboard Entry Point,但是如果你又取消了勾选那么旧的VC并不会自动又添加Storyboard Entry Point的,要小心。

高冷用法

1490498-6baf58be612c8e95.jpg

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
```![1490498-0a46dcda9e2d9982.jpg](http://upload-images.jianshu.io/upload_images/2318672-eca4b84372b52bf8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

###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,背景设置成灰色。

![1490498-e7e06b420aa167ad.jpg](http://upload-images.jianshu.io/upload_images/2318672-8003435c42d37fe8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

![1490498-bb8d92cace1eaaab.jpg](http://upload-images.jianshu.io/upload_images/2318672-a03e52aaefce99ac.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)



###IBOutletCollection(ClassName):
将基于IBOutlet创建的对象放在一个NSarray里。
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *testViewArr;
创建了一个array,里面放的是用IBOutlet创建的UIView.

注意最好用strong进行修饰,而且如果你声明的不是NSArray,即便是UIColor,系统也不会报错,你打印这个color发现,系统用的还是NSArray。这个array的顺序是连线时候的顺序,但是不排除不同版本的xcode会改变这个顺序,所以最好不要依赖这个顺序。

也可以像拖IBOutlet那样创建:

###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中我们设置的方法里了。
![1490498-39be8c63b59f1de8.jpg](http://upload-images.jianshu.io/upload_images/2318672-7f59af2081958963.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

xib文件是可以不依托于UIView子类、UIViewController子类单独使用的,只是这种情况比较少见,这是一个例子。






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

推荐阅读更多精彩内容

  • 引言 学到这里,xib给我带来的帮助已经很大了,最大的莫过于UI控件的创建、属性的赋值再也不用写代码,就UI开发来...
    二亮子阅读 7,158评论 41 82
  • 引言 下面我们来学习比xib更强大的SB 如何理解SB? 最简单的理解就是:一个.storyboard文件相当于多...
    二亮子阅读 4,088评论 11 16
  • 写在前面 我不算是个资深码农,有些iOS的编程经验。希望找到一种高效的方式来创作出自己的iOS应用。大家都知道纯代...
    五九楼阅读 13,928评论 3 40
  • 1.自定义控件 a.继承某个控件 b.重写initWithFrame方法可以设置一些它的属性 c.在layouts...
    圍繞的城阅读 2,956评论 2 4
  • 婚姻不好是伴侣的错,因为你对我这样,所以我才是这样。事业不成功,是因为别人对自己不够支持。顾客不买东西,是因为他们...
    心的新旅行阅读 186评论 0 0