純代碼寫界面有時候會下降開發效率,對於一些通用簡單的界面,例如程序設置界面,可使用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
xib文件名ide
File’s Owner佈局
xib文件中的視圖的Classui
xib文件中的視圖的Outlet指向
從哪裏加載xib,加載xib中的什麼視圖,均可以根據這幾個屬性得出。
2、Demo實踐
1. 加載xib中File’s Owner爲nil的視圖
BlueView.xib
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];
}
|
運行結果:
結論:
File’s Owner爲nil的xib文件中的視圖屬於通用視圖,在工程中能夠複用
從xib加載進來的View大小是肯定的,可是該視圖在父視圖中的位置是不肯定的,所以須要開發者自行指定
視圖中的全部子視圖會被原封不動地Load進來
2. 加載xib中File’s Owner爲self的視圖
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];
}
|
運行結果:
結論:
File’s Owner不爲nil的xib文件中的視圖屬於專用視圖,在工程中不該該被複用
只要self主動調用loadNibNamed:owner:options:方法,self持有的IBOutlet指向的視圖就會被初始化
存取xib中的視圖不用views[0]的方式,而是經過IBOutlet類型的property進行存取
3. 加載xib中File’s Owner爲特定類的視圖
RedView.xib
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];
}
|
運行結果:
結論:
File’s Owner類能夠封裝視圖中的各類邏輯,而不只僅是提供視圖內容
只要經過File’s Owner類主動調用loadNibNamed:owner:options:方法,該IBOutlet指向的視圖就會被初始化
4. 加載xib中文件名和視圖類名一致的視圖(File’s Owner爲nil)
YellowView.xib
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];
}
|
運行結果:
結論:
這裏的viewFromNib方法只是對loadNibNamed:owner:options:方法的一個簡單封裝,要求的條件包括: - xib文件名和本類類名必須一致 - 這個xib文件的File’s Owner必須爲空 - 這個xib文件必須只擁有一個視圖,而且該視圖的class爲本類
5. 經過UIViewController的initWithNibName:bundle:方法加載xib文件中的視圖
BlackView.xib
若是BlackViewController類但願self.view就是xib文件中的View,能夠在Connections頁中創建view -> File’s Owner的Outlet,以下:
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];
}
|
運行結果:
結論:
將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
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];
}
|
運行結果:
結論:
這裏我專門寫了一個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
隨便在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文件中元素的排序而已)。以下:
能夠將其打亂並從新運行程序查看結果。
參考資料:http://www.ifun.cc/blog/2014/02/22/ioskai-fa-zhi-xibji-qiao-jie-shao/