很多朋友如果是初学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方法中就可以了