XIB的是用

纯代码写界面有时候会下降开发效率,对于一些通用简单的界面,例如程序设置界面,可使用xib进行开发。
1、关于xibios

1. xib和nibgit

xib文件能够被Xcode编译成nib文件,xib文件本质上是一个xml文件,而nib文件就是编译后的二进制文件,该文件将视图等控件对象封装了起来,而在程序运行起来后,这些对象会被激活。github

xib文件本质上是一个xml文件,能够用vim或cat命令查看,例如:canvas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ cat ~/Desktop/JLN-1_xib/JLN-1_xib/GrayViewController.xib
<!--?xml version= "1.0"  encoding= "UTF-8"  standalone= "no" ?-->
<document type= "com.apple.interfacebuilder3.cocoatouch.xib"  version= "3.0"  toolsversion= "6254"  systemversion= "14b25"  targetruntime= "ios.cocoatouch"  propertyaccesscontrol= "none"  useautolayout= "yes"  usetraitcollections= "yes" >
     <dependencies>
         <plugin identifier= "com.apple.interfacebuilder.ibcocoatouchplugin"  version= "6247" >
     </plugin identifier= "com.apple.interfacebuilder.ibcocoatouchplugin"  version= "6247" ></dependencies>
     <objects>
         <placeholder placeholderidentifier= "ibfilesowner"  id= "-1"  userlabel= "file's owner"  customclass= "grayviewcontroller" >
             <connections>
                 <outlet property= "actionbutton"  destination= "edu-ds-gip"  id= "qav-o1-ta6" >
                 <outlet property= "titlelabel"  destination= "ycj-fh-rdg"  id= "xj4-yo-zzp" >
                 <outlet property= "view"  destination= "i5m-pr-fkt"  id= "sfx-zr-jgt" >
             </outlet property= "view"  destination= "i5m-pr-fkt"  id= "sfx-zr-jgt" ></outlet property= "titlelabel"  destination= "ycj-fh-rdg"  id= "xj4-yo-zzp" ></outlet property= "actionbutton"  destination= "edu-ds-gip"  id= "qav-o1-ta6" ></connections>
         
         <placeholder placeholderidentifier= "ibfirstresponder"  id= "-2"  customclass= "uiresponder" >
         <view clearscontextbeforedrawing= "no"  contentmode= "scaletofill"  id= "i5m-pr-fkt" >
             <rect key= "frame"  x= "0.0"  y= "0.0"  width= "300"  height= "44" >
             <autoresizingmask key= "autoresizingmask"  widthsizable= "yes"  heightsizable= "yes" >
             <subviews>
                 <button opaque= "no"  contentmode= "scaletofill"  fixedframe= "yes"  contenthorizontalalignment= "center"  contentverticalalignment= "center"  buttontype= "roundedrect"  linebreakmode= "middletruncation"  translatesautoresizingmaskintoconstraints= "no"  id= "edu-ds-gip" >
                     <rect key= "frame"  x= "246"  y= "7"  width= "46"  height= "30" >
                     <state key= "normal"  title= "button" >
                         <color key= "titleshadowcolor"  white= "0.5"  alpha= "1"  colorspace= "calibratedwhite" >
                     
                     <connections>
                         <action selector= "action:"  destination= "-1"  eventtype= "touchupinside"  id= "svp-jp-gk9" >
                     </action selector= "action:"  destination= "-1"  eventtype= "touchupinside"  id= "svp-jp-gk9" ></connections>
                 
                 <label opaque= "no"  userinteractionenabled= "no"  contentmode= "left"  horizontalhuggingpriority= "251"  verticalhuggingpriority= "251"  fixedframe= "yes"  text= "label"  linebreakmode= "tailtruncation"  baselineadjustment= "alignbaselines"  adjustsfontsizetofit= "no"  translatesautoresizingmaskintoconstraints= "no"  id= "ycj-fh-rdg" >
                     <rect key= "frame"  x= "129"  y= "11"  width= "42"  height= "21" >
                     <fontdescription key= "fontdescription"  type= "system"  pointsize= "17" >
                     <color key= "textcolor"  cocoatouchsystemcolor= "darktextcolor" >
                     <nil key= "highlightedcolor" >
                 
             </nil key= "highlightedcolor" ></color key= "textcolor"  cocoatouchsystemcolor= "darktextcolor" ></fontdescription key= "fontdescription"  type= "system"  pointsize= "17" ></rect key= "frame"  x= "129"  y= "11"  width= "42"  height= "21" ></label opaque= "no"  userinteractionenabled= "no"  contentmode= "left"  horizontalhuggingpriority= "251"  verticalhuggingpriority= "251"  fixedframe= "yes"  text= "label"  linebreakmode= "tailtruncation"  baselineadjustment= "alignbaselines"  adjustsfontsizetofit= "no"  translatesautoresizingmaskintoconstraints= "no"  id= "ycj-fh-rdg" ></color key= "titleshadowcolor"  white= "0.5"  alpha= "1"  colorspace= "calibratedwhite" ></state key= "normal"  title= "button" ></rect key= "frame"  x= "246"  y= "7"  width= "46"  height= "30" ></button opaque= "no"  contentmode= "scaletofill"  fixedframe= "yes"  contenthorizontalalignment= "center"  contentverticalalignment= "center"  buttontype= "roundedrect"  linebreakmode= "middletruncation"  translatesautoresizingmaskintoconstraints= "no"  id= "edu-ds-gip" ></subviews>
             <color key= "backgroundcolor"  white= "1"  alpha= "1"  colorspace= "custom"  customcolorspace= "calibratedwhite" >
             <nil key= "simulatedstatusbarmetrics" >
             <nil key= "simulatedtopbarmetrics" >
             <nil key= "simulatedbottombarmetrics" >
             <freeformsimulatedsizemetrics key= "simulateddestinationmetrics" >
             <point key= "canvaslocation"  x= "382"  y= "285" >
         
     </point key= "canvaslocation"  x= "382"  y= "285" ></freeformsimulatedsizemetrics key= "simulateddestinationmetrics" ></nil key= "simulatedbottombarmetrics" ></nil key= "simulatedtopbarmetrics" ></nil key= "simulatedstatusbarmetrics" ></color key= "backgroundcolor"  white= "1"  alpha= "1"  colorspace= "custom"  customcolorspace= "calibratedwhite" ></autoresizingmask key= "autoresizingmask"  widthsizable= "yes"  heightsizable= "yes" ></rect key= "frame"  x= "0.0"  y= "0.0"  width= "300"  height= "44" ></view clearscontextbeforedrawing= "no"  contentmode= "scaletofill"  id= "i5m-pr-fkt" ></placeholder placeholderidentifier= "ibfirstresponder"  id= "-2"  customclass= "uiresponder" ></placeholder placeholderidentifier= "ibfilesowner"  id= "-1"  userlabel= "file's owner"  customclass= "grayviewcontroller" ></objects>
</document type= "com.apple.interfacebuilder3.cocoatouch.xib"  version= "3.0"  toolsversion= "6254"  systemversion= "14b25"  targetruntime= "ios.cocoatouch"  propertyaccesscontrol= "none"  useautolayout= "yes"  usetraitcollections= "yes" >

nib文件能够在程序的Build目录下找到。vim

2. xib文件的若干属性数组

xib文件有如下几个重要的属性:app

  1. xib文件名ide

  2. File’s Owner布局

  3. xib文件中的视图的Classui

  4. xib文件中的视图的Outlet指向

从哪里加载xib,加载xib中的什么视图,均可以根据这几个属性得出。

2、Demo实践

Demo传送门

1. 加载xib中File’s Owner为nil的视图

BlueView.xib

blueview_xib.png

MainViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
@property (strong, nonatomic) UIView *blueView;
...
- (void)loadBlueViewFromXIB {
     // BlueView.xib的File's Owner为nil
     NSArray *views = [[NSBundle mainBundle] loadNibNamed:@ "BlueView"  owner:nil options:nil];
     self.blueView = views[0];
     
     // 从xib加载进来的View大小是肯定的,可是该视图在父视图中的位置是不肯定的
     // 此外,视图中的子视图也是原封不动地Load进来的
     CGRect rect = _blueView.frame;
     rect.origin.x += 37.5f;
     rect.origin.y += 80.0f;
     _blueView.frame = rect;
     [self.view addSubview:_blueView];
}

运行结果:

302.jpg

结论:

  • File’s Owner为nil的xib文件中的视图属于通用视图,在工程中能够复用

  • 从xib加载进来的View大小是肯定的,可是该视图在父视图中的位置是不肯定的,所以须要开发者自行指定

  • 视图中的全部子视图会被原封不动地Load进来

2. 加载xib中File’s Owner为self的视图

greenview_xib.png

MainViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
@property (weak, nonatomic) IBOutlet UIView *greenView;
...
- (void)loadGreenViewFromXIB {
     // GreenView.xib的File's Owner设为self,并创建了一个从该xib的View到self的IBOutlet greenView
     [[NSBundle mainBundle] loadNibNamed:@ "GreenView"  owner:self options:nil];
     
     // 只要self主动调用Load XIB的方法,self持有的IBOutlet指向的视图就会被初始化
     // 这里不须要经过views[0]的方式存取视图
     CGRect rect = _greenView.frame;
     rect.origin.x = _blueView.frame.origin.x;
     rect.origin.y = _blueView.frame.origin.y + 80.0f;
     _greenView.frame = rect;
     [self.view addSubview:_greenView];
}

运行结果:

greenview.png

结论:

  • File’s Owner不为nil的xib文件中的视图属于专用视图,在工程中不该该被复用

  • 只要self主动调用loadNibNamed:owner:options:方法,self持有的IBOutlet指向的视图就会被初始化

  • 存取xib中的视图不用views[0]的方式,而是经过IBOutlet类型的property进行存取

3. 加载xib中File’s Owner为特定类的视图

RedView.xib

redview_xib.png

RedViewOwner.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface RedViewOwner : NSObject
@property (strong, nonatomic) IBOutlet UIView *redView;
MainViewController.m
...
@property (strong, nonatomic) RedViewOwner *redViewOwner;
...
- (void)loadRedViewFromXIB {
     // RedView.xib的File's Owner是RedViewOwner类的实例,并创建了一个从该xib的View到RedViewOwner实例的IBOutlet
     // 只要经过_redViewOwner主动调用Load XIB的方法,该IBOutlet指向的视图就会被初始化
     self.redViewOwner = [RedViewOwner  new ];
     [[NSBundle mainBundle] loadNibNamed:@ "RedView"  owner:_redViewOwner options:nil];
     
     UIView *redView = _redViewOwner.redView;
     CGRect rect = redView.frame;
     rect.origin.x = _greenView.frame.origin.x;
     rect.origin.y = _greenView.frame.origin.y + 80.0f;
     redView.frame = rect;
     [self.view addSubview:redView];
}

运行结果:

45.jpg

结论:

  • File’s Owner类能够封装视图中的各类逻辑,而不只仅是提供视图内容

  • 只要经过File’s Owner类主动调用loadNibNamed:owner:options:方法,该IBOutlet指向的视图就会被初始化

4. 加载xib中文件名和视图类名一致的视图(File’s Owner为nil)

YellowView.xib

yellowview_xib.png

YellowView.h/m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@interface YellowView : UIView
+ (instancetype)viewFromNIB;
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@end
@implementation YellowView
// Convenience Method
+ (instancetype)viewFromNIB {
     // 加载xib中的视图,其中xib文件名和本类类名必须一致
     // 这个xib文件的File's Owner必须为空
     // 这个xib文件必须只拥有一个视图,而且该视图的class为本类
     NSArray *views = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil];
     return  views[0];
}
- (void)awakeFromNib {
     // 视图内容布局
     self.backgroundColor = [UIColor yellowColor];
     self.titleLabel.textColor = [UIColor whiteColor];
}
@end
MainViewController.m
...
@property (strong, nonatomic) YellowView *yellowView;
...
- (void)loadYellowViewFromXIB {
     // 说明见YellowView.m的viewFromNIB方法
     self.yellowView = [YellowView viewFromNIB];
     
     CGRect rect = _yellowView.frame;
     UIView *redView = _redViewOwner.redView;
     rect.origin.x = redView.frame.origin.x;
     rect.origin.y = redView.frame.origin.y + 80.0f;
     _yellowView.frame = rect;
     [self.view addSubview:_yellowView];
}

运行结果:

46.jpg

结论:

这里的viewFromNib方法只是对loadNibNamed:owner:options:方法的一个简单封装,要求的条件包括: - xib文件名和本类类名必须一致 - 这个xib文件的File’s Owner必须为空 - 这个xib文件必须只拥有一个视图,而且该视图的class为本类

5. 经过UIViewController的initWithNibName:bundle:方法加载xib文件中的视图

BlackView.xib

blackview_xib_custom.png

若是BlackViewController类但愿self.view就是xib文件中的View,能够在Connections页中创建view -> File’s Owner的Outlet,以下:

blackview_xib_connections.png

BlackViewController.h/m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface BlackViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
// Convenience Method
+ (instancetype)viewControllerFromNIB;
@end
@implementation BlackViewController
- (void)viewDidLoad {
     [ super  viewDidLoad];
     
     self.view.backgroundColor = [UIColor blackColor];
     self.titleLabel.textColor = [UIColor whiteColor];
}
+ (instancetype)viewControllerFromNIB {
     return  [[BlackViewController alloc] initWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
}
- (void)didReceiveMemoryWarning {
     [ super  didReceiveMemoryWarning];
     // Dispose of any resources that can be recreated.
}
@end

MainViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
@property (strong, nonatomic) BlackViewController *blackViewController;
...
- (void)loadBlackViewFromXIB {
     self.blackViewController = [[BlackViewController alloc] initWithNibName:@ "BlackViewController"  bundle:[NSBundle mainBundle]];
     
     // 或使用Conveniece Method,但要求xib文件名和View Controller类名一致
     // self.blackViewController = [BlackViewController viewControllerFromNIB];
     
     UIView *blackView = _blackViewController.view;
     CGRect rect = blackView.frame;
     rect.origin.x = _yellowView.frame.origin.x;
     rect.origin.y = _yellowView.frame.origin.y + 80.0f;
     blackView.frame = rect;
     [self.view addSubview:blackView];
}

运行结果:

blackview.png

结论:

  • 将xib的File’s Owner设成一个UIViewController子类,能够将这个xib文件的视图展现和外部响应事件(例如点击一个按钮触发的点击事件,该视图的手势事件等)所有封装在一个View Controller中,若是把按钮的点击事件封装在一个UIView类中,貌似破坏了MVC模式,所以最好将xib的File’s Owner设成一个UIViewController子类,该类能够经过addChildViewController方法将其添加到现有的View Controller上。若是只是但愿加载视图,能够经过viewcontroller.view存取。

若是但愿ViewControllerA加载并响应aXIBView中的按钮点击事件,这时必须创建一个aXIBView到ViewControllerA的IBAction,若是ViewControllerA须要拥有多个这样的XIB,那么ViewControllerA会变得很是的庞大,此时能够经过为每个XIB设置一个ViewController,再让ViewControllerA加载这些Child View Controllers,这样能够将这些事件的响应职责和视图的描绘工做分派给专门的Child View Controller,在减少ViewControllerA体积的同时,也能够提升各个xib的可复用性。

  • 这里的viewControllerFromNIB方法其实就是initWithNibName:bundle:方法的一个简单封装,要求:xib的File’s Owner设为本类。

6. 经过UIViewController+NIB加载xib文件中的View Controller类和其视图

GrayView.xib

QQ截图20150202102345.jpg

UIViewController+NIB.h/m

1
2
3
4
5
6
7
8
9
10
11
12
@interface UIViewController (NIB)
// 要求xib文件名和View Controller类名一致
+ (instancetype)loadFromNib;
@end
@implementation UIViewController (NIB)
+ (instancetype)loadFromNib {
     // [self class]会由调用的类决定
     Class controllerClass = [self class];
     NSLog(@ "class = %@" , controllerClass);
     return  [[controllerClass alloc] initWithNibName:NSStringFromClass(controllerClass) bundle:[NSBundle mainBundle]];
}
@end

GrayViewController.h/m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@interface GrayViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UIButton *actionButton;
@end
@implementation GrayViewController
- (void)viewDidLoad {
     [ super  viewDidLoad];
     
     self.view.backgroundColor = [UIColor grayColor];
     
     self.titleLabel.text = @ "Gray View" ;
     self.titleLabel.textColor = [UIColor whiteColor];
     self.titleLabel.textAlignment = NSTextAlignmentCenter;
     self.titleLabel.font = [UIFont systemFontOfSize:8.5f];
     
     [self.actionButton setTitle:@ "action"  forState:UIControlStateNormal];
     [self.actionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
}
- (void)didReceiveMemoryWarning {
     [ super  didReceiveMemoryWarning];
     // Dispose of any resources that can be recreated.
}
// 推荐从XIB文件中加载View Controller的方法,这种方法能够将XIB文件中的视图和其按钮响应事件所有封装在GrayViewController
// 若是GrayViewController的按钮响应事件由MainViewController做出响应,那么两者的耦合度就太高
// 建议:
// 单纯的通用View展现,使用从xib文件加载视图的方法,File's Owner设为nil
// 特定拥有者的View展现,从xib文件加载视图时,File's Owner设为拥有者
// 若是视图中有按钮响应事件,或其它能够和用户交互的事件,建议采用从XIB文件中加载View Controller的方法,这样能够封装UI展现和交互事件
- (IBAction)action:(id)sender {
     NSLog(@ "action" );
}
@end

MainViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
@property (strong, nonatomic) GrayViewController *grayViewController;
...
- (void)loadGrayViewFromXIB {
     self.grayViewController = [GrayViewController loadFromNib];
     
     UIView *grayView = _grayViewController.view;
     UIView *blackView = _blackViewController.view;
     CGRect rect = grayView.frame;
     rect.origin.x = blackView.frame.origin.x;
     rect.origin.y = blackView.frame.origin.y + 80.0f;
     grayView.frame = rect;
     [self.view addSubview:grayView];
}

运行结果:

grayview.png

结论:

这里我专门写了一个UIViewController+NIB的category,只须要调用loadFromNib类方法就能够加载xib中的视图。要求: - xib文件的File’s Owner必须设置为对应的View Controller类。

3、总结

在写界面时同时混用xib和代码能够提升效率,而对xib的使用主要体如今其专用性和通用性上。

  • 对于一些专门的界面,例如App中的设置界面,纯代码写不免会浪费时间,此时能够经过xib文件的拖控件方法来定制。这个xib是专用于某一个界面的,目的是提升效率。

  • 对于一些通用的控件甚至界面,例如一个很漂亮但实现起来很是复杂的按钮,此时能够经过load xib文件中的视图来快速添加。这个xib对于全部视图是共用的,目的是提升可复用性。

对于通用的xib:

  • 若是xib只是单纯的界面展现,那么File’s Owner能够随意。

  • 若是xib中包含了按钮、手势等用户输入事件,那么File’s Owner最好设置为UIViewController类的子类。

4、自问自答

之前使用xib时一直都有点疑问,xib中能够有多个视图控件,可是从xib中load出来的是一个数组,那么怎么肯定哪一个对象对应的是哪一个控件呢?

能够实践一下:

PurpleView.xib

purpleview_xib.png

随便在xib文件中加了几个视图。

接下来将其load出来看看:

MainViewController.m

1
2
3
4
5
6
7
8
9
- (void)logViewsFromXIB {
     NSLog(@ "%s begin" , __func__);
     NSArray *views = [[NSBundle mainBundle] loadNibNamed:@ "PurpleView"  owner:nil options:nil];
     for  (int i = 0; i < views.count; i++) {
         id obj = views[i];
         NSLog(@ "%d : %@" , i, [obj class]);
     }
     NSLog(@ "%s end" , __func__);
}

控制台输出以下:

1
2
3
4
5
6
2015-01-09 15:03:06.629 JLN-1_xib[3139:121677] -[MainViewController logViewsFromXIB] begin
2015-01-09 15:03:06.635 JLN-1_xib[3139:121677] 0 : UIView
2015-01-09 15:03:06.635 JLN-1_xib[3139:121677] 1 : UIButton
2015-01-09 15:03:06.636 JLN-1_xib[3139:121677] 2 : UITableView
2015-01-09 15:03:06.636 JLN-1_xib[3139:121677] 3 : UILabel
2015-01-09 15:03:06.636 JLN-1_xib[3139:121677] -[MainViewController logViewsFromXIB] end

结论:

从xib中load出来的views数组中视图对象的排列顺序和xib scene中的对象排列顺序一致(其实就是xml文件中元素的排序而已)。以下:

purpleview_xib_scene.png

能够将其打乱并从新运行程序查看结果。

参考资料:http://www.ifun.cc/blog/2014/02/22/ioskai-fa-zhi-xibji-qiao-jie-shao/

相关文章
相关标签/搜索