用xib创建和加载一个View

看前必看:下述中有个MyView1,你就当做MyView就行了,因为我们没有MyView2或者MyView3。这是由于之前测试的原因造成的。

一、用代码加载一个xib创建的view

这个大家都比较熟悉:

  1. 新建一个MyView1,对应增加.h和.m文件。
  2. 新建一个MyView1.xib文件。如图:
  3. 在.xib中选中View,右边的Class填入MyView1。
  4. 此时,在.xib的view中的控件就可以拖线到.h或者.m文件中了。
  5. init方法里面加入获取xib的代码:

    1
    2
    3
    4
    5
    6
    - (instancetype)init {
    self = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil].lastObject;
    if (self) {
    }
    return self;
    }
  6. 在另一个地方用代码获取MyView1:

    1
    2
    3
    MyView1 *view = [MyView1 new];
    view.frame = CGRectMake(20, 100, 375-40, 118);
    [self.view addSubview:view];

注意:
步骤3只是把.xib中的view对应的Class标为MyView1,这样才能进行步骤4。
步骤5是必要的,因为init方法只是在代码层面获取到self,并未跟.xib关联。而步骤3中的关联仅仅是为了步骤4中的拖线能够进行。

二、用xib加载另一个由xib创建的view

  1. 现在我们在的基础上进行这一步。注意,此时我先注释步骤5和步骤6中的代码,以免造成干扰。
  2. 我们的目的是先定义好了这个MyView1及其.xib,接着,在viewController的.xib的根view中,拖入一个view控件,然后选择这个view,在右边的Class选项中填入MyView1。我们想在项目跑起来后,在界面上能看到MyView1对应的控件。但是好像并没有什么卵用。
  3. 界面上什么都没有,也就是说viewController.xib中拖入的view并未跟MyView产生关联,我们所做的仅仅只是把其Class对应为MyView1而已。

经过各种百度(用xib加载另一个xib)、Google(load xib from another xib)。搜到一些有用的文章:
http://www.cnblogs.com/sirkevin/p/4108163.html
https://www.jianshu.com/p/371b1a195fb9
https://github.com/PaulSolt/CompositeXib

然后总结一下正确的使用方法:

  1. 同上1。
  2. 同上2。
  3. 选中MyView1.xib,点击File`s Owner,在右边的Class选项中填入MyVIew1。
  4. 同上4。
  5. initWithCoder:方法中加入以下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
    MyView1 *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil].lastObject;
    view.frame = self.bounds;
    [self addSubview:view];
    }
    return self;
    }

6、运行项目,发觉一切如所愿。

注意:
步骤3也仅仅是为步骤4服务的,但是注意这里不同于中的步骤3。如果和中一样,会造成代码调用死循环,原因目前还不是很清楚。
步骤5中,方法换成了initWithCoder:,原因是:任何从资源文件中加载视图时,走的都是initWithCoder:方法,而不会走init方法。 但是,其写法跟中的也不一样。不能直接指定self = [NSBunde …].lastObject,不然会报错,错误原因是:reason: ‘This coder requires that replaced objects be returned from initWithCoder: 即,self必须是initWithCoder的返回值,不能用其他的代替。所以,这里只能用addSubview的方式添加。
但是,如此一来,就造成了一些问题。假设,我们想修改MyView1的背景颜色,怎么弄?由于MyView1我们假定是一个公共类,那么我们只能在外部来修改它。于是,在viewController.xib中,我们把这个view拖线到.m文件中,取名为myView,然后在viewDidLoad中加入代码:_myView.backgroundColor = [UIColor redColor]; 运行项目发觉没有任何作用。打开视图层次检测器,发觉如下图:

红色部分的确是MyView1,但是绿色的view只是普通的UIView,跟MyView1其实没有任何关系,唯一的关系就是步骤3、4里指出的。所以,我们任何想要修改MyView1的属性的操作,都必须在MyView1.m中重写方法,从而能够作用到它的子视图—绿色的view中。为了操作方便,我们在MyView1.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
#import "MyView1.h"

@interface MyView1 ()
@property (nonatomic, strong) MyView1 *view;
@end

@implementation MyView1

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil].lastObject;
_view.frame = self.bounds;
[self addSubview:_view];
}
return self;
}

- (void)setBackgroundColor:(UIColor *)backgroundColor {
_view.backgroundColor = backgroundColor;
[super setBackgroundColor:backgroundColor];
}

@end

我们给xib对应的视图声明了一个属性 view,这看起来很奇怪,就好像把自己指定为自己的属性。其实,我们只要认为 MyView1.h.m != MyView1.xib,就不会有这个疑惑了。
重新run一下,一切正常。
同时,我们在.m文件中,访问view的子控件,比如标题和正文,都还是跟原来一样。

三、创建一个xib的view,同时满足可以用代码创建它,和用xib创建它

这个确实有点麻烦。
此时MyView1.xib的配置还是跟保持不变。然后我们把标题拖线到.h文件中:

1
2
3
4
#import <UIKit/UIKit.h>
@interface MyView1 : UIView
@property (weak, nonatomic) IBOutlet UILabel *title;
@end

然后.m中把init方法加上:

1
2
3
4
5
6
- (instancetype)init {
self = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil].lastObject;
if (self) {
}
return self;
}

在viewController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)viewDidLoad {
[super viewDidLoad];

// 代码创建的
MyView1 *view = [MyView1 new];
view.frame = CGRectMake(20, 100, 375-40, 118);
[self.view addSubview:view];
view.backgroundColor = [UIColor orangeColor];
view.title.text = @"你好啊";

// xib创建的
_myView.backgroundColor = [UIColor redColor];
_myView.title.text = @"你好就好";
}

结果报错: *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIView title]: unrecognized selector sent to instance 0x7fb944424080’

很明显,是在用代码创建的那一部分报的错。因为MyView1的init方法中的self是[NSBundle …].lastObject,意思是它指代的就是如下图:

,指的是灰色选中部分,而它所属的类并非是MyView1,因为我们已经把File`s Owner值为MyView1了,而且为了使得用xib创建的代码不崩溃,我们也不能指定图中的view的Class为MyView1。

所以,办法只能是这样:

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
#import "MyView1.h"

@interface MyView1 ()
@property (nonatomic, strong) MyView1 *view;
@end

@implementation MyView1

- (instancetype)init {
self = [super init];
if (self) {
[self _loadView];
}
return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self _loadView];
}
return self;
}

- (void)_loadView {
_view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil].lastObject;
_view.frame = self.bounds;
[self addSubview:_view];
}

- (void)setBackgroundColor:(UIColor *)backgroundColor {
_view.backgroundColor = backgroundColor;
[super setBackgroundColor:backgroundColor];
}

@end

也就是:init方法作一下让步。然后run一下,一切如愿。

这里也总结出:

  1. 在.xib中,指定谁的Class为MyView1的结果是完全不同的。
  2. initWithCoder:方法中加载.xib时,我们必须要指定File`s Owner的Class。否则会造成死循环,因为一直无法load成功。
    不要认为MyView1.h.m就等于MyView.xib。

项目地址:
下载代码