背景
最近在做需求的时候,遇到了一个事情:某个自定义view的awakeFromNib()函数中爆出了如下错误
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
原因是函数中使用了xib中的一个outlet。于是就有了今天的问题,awakeFromNib()不是在所有对象都解档完毕后才发送的么,走到这里而它的子view还没有初始化是为什么,平时我们自定义view和xib的关联又是怎么回事。带着这些问题我们往下看个例子
代码使用xib
要实现自定义view和xib的结合,首先我们要有如下的两个文件
subView和xib
如何让这两个文件关联起来呢 我们选中xib的属性面板 然后将view的custom class改为自定义View的类型:
更改自定义view的类型
然后我们给自定义view加一个outlet 并在awakeFromNib()中对它做一些配置
配置Outlet
之后我们在使用的地方使用如下代码调用
let owner = SubView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let loadedView = Bundle.main.loadNibNamed("SubView", owner: owner, options: nil)?.first as! SubView
self.view.addSubview(loadedView)
手动加载view的xib
我们可以看到view的xib已经正确加载了 当然我们也可以传递一个nil的owner给加载函数也能成功,例如
let loadedView = Bundle.main.loadNibNamed("SubView", owner: nil, options: nil)?.first as! SubView
self.view.addSubview(loadedView)
xib使用xib
那么如果我想在xib中使用这个自定义view的xib呢?大家第一想法应该是在使用的xib属性中更改view的class 例如:
xib中设置其他view的自定义类型
这个时候我们如果运行就会出现如下错误
访问子控件失败
这就是我们开头讲到的问题,让我们先来看看究竟是什么原因导致的。首先看下我们的堆栈
崩溃堆栈
从图里可以看到,我们崩溃之前只调用了instantiateWithOwner:options:这个方法用来解档viewController对应的nib文件,之后就给我们对应的自定义view发送了awakefromNib的消息,也就是整个过程中,SubView的xib文件并没有进行解档,而是直接收到解档完成的消息。
原理
为什么Subview.xib并没有解档却收到了消息呢,我们还要从xib的解档来说,当我们在父view的xib中将某个view设置为自定义view的类型的时候,会发生什么呢,首先,父view的xib会将这一个view归档到xib中。那么归档的内容是什么呢?
因为view的其他子view都在subView.xib中,所以父view的xib只会归档这个空的view,所以解码的时候自然subView下边没有任何的子view。
有人会问了。subview和xib不是已经关联了么。那么加载xib的时候为什么没有加载subview.xib中的内容而是直接崩溃了呢?注意我们之前所说的,当subView收到消息的时候,并不是subView.xib解档完成,而是父xib接档完成发送的消息。所以问题就出现了。
正确用法
如何在xib中使用另一个自定义的xib呢
两个步骤,
-
在自定义view中将view和xib的fileOwner关联起来
关联xib和fileOwner
2.在自定义view的initWithCoder中加载view的xib
required init?(coder: NSCoder) {
super.init(coder: coder)
Bundle.main.loadNibNamed("SubView", owner: self, options: nil)?.first as! UIView
self.addSubview(self.view)
}
这个时候我们就可以开心的使用了
总结
大家使用的时候一定要注意各个view和nib和生命周期和使用过程。随意使用就只有崩溃了。要多学习多总结。
网友评论