很多朋友如果是初學iOS開發,可能會被其中的幾個加載方法給搞得暈頭轉向的,但是這幾個方法又是作為iOS程序員必須要我們掌握的方法,下面我將對這幾個方法做一下分析和對比,看看能不能增加大家對幾個方法的理解和使用.
首先是常用的加載方法有:
- initWithNibName:bundle: (加載帶有XIB的控制器)
- loadView (控制器的View為空的時候調用,幫控制器加載View)
- initWithCoder: (是當從nib文件中加載對象的時候會調用)
- awakeFromNib (當.nib文件被加載的時候,會發送一個awakeFromNib的消息到.nib文件中的每個對象)
- initWithFrame: (代碼創建View時調用,是懶加載,只有到需要顯示時,子控件才不是 nil)
- init(代碼使用創建控件alloc init 時,系統底層調用init方法)
首先我們開始從控制器(viewController)的加載開始說起:
??我們加載控制器可以使用代碼也可以使用storyboard
----NO1. 對于使用代碼加載控制器(這里是創建控制器哦,不是創建UIView,關于創建UIView另外再談),這里的ViewController帶有一個xib(storyboard,系統默認加載的,相當于顯示的是控制器的view)
ViewControllerWith *vc = [[ViewControllerWith alloc] init];
在這個加載過程中,相關方法調用順序是:
- init
- initWithNibName:bundle: - 加載帶有xib的控制器(默認ViewController)
- loadView - 加載控制器視圖
- viewDidLoad - 加載完畢
----NO2. 上面講了加載storyboard中的控制器,我們現在加載自定義控制器并且帶有xib的情況(也就是當我們Command + N,勾選also create XIB file的情況)
注意哦:看看后綴名,第一種情況是加載在storyboard中的控制器(默認是main.storyboard),現在講的是后綴.xib的控制器,兩個概念哈 - 我們系統默認加載是main.storyboard中的xib,區別是里面都是控制器,可以設置箭頭
看看上面的加載控制器的圖片,加載控制器可以設置箭頭的哦,箭頭的設置表示默認加載箭頭指向的的控制器,但是我們現在講的這種情況是加載自定義控制器的xib的情況(這種xib其實是UIVIew表示使用xib中的View去代替控制器的view來顯示,看圖):
在這里圖1-3和圖4-5一個是storyboard中一個是xib中,前者是控制器,后者本質是UIView,使用xib表示UIView去顯示控制器的view,兩者之間有區別的哦不要搞錯了
繼續說第二種情況,對于第二種情況的控制器加載是:
BackViewController *backVC = [[BackViewController alloc] initWithNibName:@"BackViewController" bundle:nil];
在加載過程中,相關方法調用順序是:
- initWithNibName:bundle:
- loadView
- viewDidLoad
對于上面的這種情況,我專門做了一個小例子,看代碼:
在這里我使用純代碼:就是不使用系統默認加載的main.storyboard中的控制器,自己加載想要加載的控制器(OC程序員必備技能)
首先是去掉main.storyboard中的控制器,去掉viewController:自定義控制器并且加載它
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
JNTestViewController *vc = [[JNTestViewController alloc] initWithNibName:@"jtest" bundle:nil];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
在這里我自定義控制器,并且將之設置為window的根控制器,當然我使用(initWithNibName)去加載我指定文件(控制器和view)
注意:這里關于xib的加載,我們要知道,我在這里是將xib文件命名為jtest,但是一般來說,創建自定義控制器的時候xib的命名是和自定義控制器是同名的,如果xib和自定義控制器同名,那么此時我們就可以直接init創建不需要指定xib的名字:
JNTestViewController *vc = [[JNTestViewController alloc] init];
可以這樣做的原因是,系統在底層首先調用init方法,在init方法內部自動會調用(initWithNibName)方法,首先系統先看看是否有指定名字的xib,如果沒有就加載控制器同名但是去掉Controller的xib,還沒有就加載與控制器同名的xib
----NO3. storyboard中加載控制器使用的相關方法
前面說了,在后綴名是storyboard中加載的是控制器,可以設置箭頭來指定默認想要系統去加載的控制器,那么如果使用代碼區加載想要加載的控制器呢,看代碼:
首先在Main.storyboard中去掉viewController的箭頭,添加id,如圖:
在指定了id之后,根據id加載指定的控制器,看代碼:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//1.創建窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];//nil表示從mainBundle中取資源
ViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"jntest2"];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
依舊是使用純代碼,這種做法首先是指定想要加載的storyboard中的控制器的id,然后根據id加載指定的控制器,關鍵是(instantiateViewControllerWithIdentifier)方法的使用
在這個加載過程中 相關方法調用順序是:
- initWithCoder:
- awakeFromNib
- loadView(控制器的View的子控件也是在控制器實例化之后加載view)
- viewDidLoad
OKay,在上面講了關于加載控制器,下面呢我們將要講講關于加載UIView調用的方法
----NO1.首先是純代碼加載:
在這個加載過程中 相關方法調用順序是:
- init
- initWithFrame:
- 是init調用了initWithFrame:
----NO2. xib的方式加載UIView
實現自定義xib - MyView.xib,如下圖:
在控制器中加載自定義的xib,將xib顯示在控制器的view上,看代碼:
- (void)viewDidLoad {
UIView *myViews = [[[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil] lastObject];
myViews.frame = CGRectMake(10, 10, 100, 100);
[self.view addSubview:myViews];
}
在對控制器和view的加載有所了解之后,我們接著來談論幾個方法之間兩兩的區別
NO1. init和initWithFrame方法
首先當代碼創建控件時,會有init,此時會底層調用init方法,但是init又會在內部調用initWithFrame方法,總的來說,兩個方法中作用都是對控件進行創建,在實際開發中可以將控件的創建直接寫在initWithFrame方法即可
NO2. initWithFrame和initWithCoder方法
我們在創建UIVIew的時候,一般會使用兩種方式:一種是代碼,一種是拖控件(interface builder也就是使用nib文件的方式),我們時候拖控件的方式此時initWithFrame方法不會被調用,因為nib文件知道如何初始化該view(拖控件的時候已經定義好了長度高等屬性),使用拖控件的方式會調用initWithCoder方法,在該方法中可以重新定義我們在nib中已經設置的各項屬性
在使用代碼進行view的創建的時候需要注意:當我們創建UIView的子類的時候,我們使用initWithFrame方法實例化UIVIew,并且特別注意:如果在子類中重載initWithFrame方法,必須先調用父類的initWithFrame方法,否則會出現一些意想不到的問題,看看使用initWithFrame創建的一般代碼格式:
JNView.m:
#import "JNView.h"
@implementation JNView
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
//在該方法中進行初始化設置
return self;
}
@end
簡單點:initWithoder 是當從nib文件中加載對象的時候會調用;initWithFrame是初始化并返回一個新的視圖對象,在使用代碼創建對象的時候調用.由此:當我們在自定義控件的時候(使用代碼創建),一般的套路都是先在init或者initWithFrame方法中做子控件的創建和初始化,在layoutSubviews方法中進行子控件的布局,然后再重寫子控件的setter方法給子控件設置數據
NO3. initWithNibName 和 loadNibNamed 方法
我的理解是,使用initWithNibName時加載的是控制器,使用loadNibNamed時加載的是控件比如UIView,UIButton,并且由于在一個xib中可以有多個控件,所以該方法返回的是數組
NO4. initWithCoder:和awakeFromNib方法
- initWithCoder: 只要對象是從文件(xib或storyboard)解析來的,就會調用
- awakeFromNib 從xib或者storyboard加載完畢就會調用
- 同時存在會先調用initWithCoder:
那么關于initWithCoder和awakeFromNib的關系:
首先調用initWithCoder加載xib或者storyboard,然后方法內部發送awakeFromNib消息給每個xib中的對象將對象喚醒,也就是說如果xib中手動拖拽了一個UIView在initWithCoder方法中該UIView是處于未被喚醒狀態,此時在initWithCoder方法中去手動向UIView的子控件添加控件,手動添加的控件不會被顯示
簡單點:就是說在initWithCoder方法中添加子控件是可以顯示,但是添加子控件的子控件不被顯示,再簡單點的話就是說以后想要手動添加控件在xib中請直接寫在awakeFromNib方法中
可能還有些朋友對這兩個方法的區別還是有點暈,沒關系,我做了一個例子,一起看看以上兩個方法在具體使用上的區別:
我自定義UIView并且關聯xib,看下圖:
提供一個類工廠方法快速創建該自定義UIView
+ (instancetype)uiview
{
return [[[NSBundle mainBundle] loadNibNamed:@"JNUIView" owner:nil options:nil] lastObject];
}
匿名分類中拿到圖中拖拽的UIView,并且定義了兩個UILabel
@interface JNUIView()
@property (weak, nonatomic) IBOutlet UIView *cyView;
@property(weak, nonatomic) UILabel *JNLabel;
@property(weak, nonatomic) UILabel *JNLabel1;
@end
我先在initWithCoder中創建一個子控件(手動的哦),并且該控件是添加在當前的xib自身中不是子控件上
//創建子控件
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
UILabel *label = [[UILabel alloc] init];
label.backgroundColor = [UIColor blueColor];
label.text = @"JN";
[self addSubview:label];
self.JNLabel = label;
//-----------
}
return self;
}
結果如下:
我向子控件cyView中添加控件,看看結果
//創建子控件
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
UILabel *label = [[UILabel alloc] init];
label.backgroundColor = [UIColor blueColor];
label.text = @"JN";
[self addSubview:label];
self.JNLabel = label;
//-----------
UILabel *label1 = [[UILabel alloc] init];
label1.backgroundColor = [UIColor yellowColor];
label1.text = @"yellow";
[self.cyView addSubview:label1];
self.JNLabel1 = label1;
}
return self;
}
上面的結果顯示向子控件cyVIew中添加子控件是不行的,那么如果我非要向cyView中添加子控件該怎么辦? - 利用awakeFromNib,看代碼:
-(void)awakeFromNib
{
[super awakeFromNib];
UILabel *label1 = [[UILabel alloc] init];
label1.backgroundColor = [UIColor yellowColor];
label1.text = @"yellow";
[self.cyView addSubview:label1];
self.JNLabel1 = label1;
}
結果如何,看下圖:
結果已經顯示了,利用awakeFromNib可以實現向xib的子控件中添加子控件,因為此時子控件已經被喚醒可以添加子控件的子控件的,總之記住:創建控件寫在awakeFromNib方法中就可以了