前一阵子在探究XCode12 + iOS14的UITableViewCell ContentView 显示层级bug时发现了一个问题:
当CollectionViewCell 是由Xib加载的,就不会有问题。
由此开始了对Xib是如何加载出来的探究。
先说大家都知道的:
(1).xib的本质是一个xml文件, 在xib文件上右键[Open as]->[Source Code]可以查看其xml源码。
(2).xib创建View 走的是 init?(coder: NSCoder)
方法,纯代码走的是init(frame: CGRect)
那么标准库又是怎么通过init?(coder: NSCoder)
把视图加载出来的呢?
下面会从流程和解析两方面来讲Xib是如何加载显示的
流程
系统在编译的时候,会将xib文件编译生成.nib文件,这个在app的ipa文件里能找到。
先用xib创建一个UIView
的子类NibView
我们在项目里加载一个xib的写法:
let nibView = Bundle.main.loadNibNamed("NibView", owner: self, options: nil)?.first as! NibView
view.addSubview(nibView)
当执行代码loadNibNamed
的时候只做了三件事:
-
根据你的NibName找到nib文件, 再通过UINib(dataforpath: nibPath) 方法生成data数据。
// 获取Bundle path let nibPath = Bundle.main.path(forResource: "NibView", ofType: "nib") // 私有api 通过path 生成data数据 let nibData = UINib(dataforpath: nibPath)
-
UINibDecoder(系统私有类) 会根据data数据创建自己的UINibDecoder实例,
这里的UINibDecoder实例也就是init?(coder: NSCoder)
传入的coder。// UINibDecoder(私有类)通过data创建实例 let coder = UINibDecoder(readingData:nibData, error: error)
-
最后通过第二步获得的coder,调用对应NibView的init?(coder: NSCoder) 方法创建视图。
let nibView = NibView(coder: coder)
至此一个xib生成的View就创建了出来。
那么我还有一个疑问?
UIView 又是如何通过 init?(coder: NSCoder) 去创建具体的视图的呢?
我们往之前创建的NibView的UIView的子类 往上面添加一个NibButton,如图:
image.png
查看其xml文件,其中红色部分是当前View的信息,黄色部分是subviews的标签,不用细看,下面会讲 :
image.png当调用 init?(coder: NSCoder)
时, 通过coder的decodeObject(forKey: String)
方法。获取对应的UI信息。 这里的key是系统规定好的一些key。通过尝试发现了下面一些有用的key。
class NibView: UIView {
required init?(coder: NSCoder) {
super.init(coder: coder)
/// 这里的coder为UINibDecoder 类 isSameClass为 true
let isSameClass = coder.isKind(of: NSClassFromString("UINibDecoder")!)
// coder 通过"UISubviews" key 获取到子视图的信息,在cocoa系统中key为"NSSubviews"
let nibSubviews = coder.decodeObject(forKey: "UISubviews")!
print(nibSubviews)
}
}
此时控制台打印信息为
image.png
如果在xml文件的subviews标签下修改 button 的属性,这里打印出的信息也会跟着改变。
那么是不是subviews标签的内容就对应着之前coder的key "UISubviews"
?
这里是不一定。在一些UIView的子类如会有一些其他处理,如tableviewCell。
像tableviewCell会外部自动包裹一层contentView,contentView的中coder的key "UISubviews"
所对应的内容才是subviews标签下的内容。
tableviewCell的xib文件, 其他都不用看,知道几个框框的对应的标签就行:
image.png
不过可以肯定的是都是通过coder 都是通过 读取 key"UISubviews"
的值来获取子视图。
然后添加到视图上。
到这里,xib是如何加载并显示到view上整个过程已经明了了。
还有像下面一些有趣key可以解析到对应的UI属性信息,在cocoa
中将UI前缀改成NS前缀即可。
coder.decodeBool(forKey: "UIOpaque")
coder.decodeBool(forKey: "UIHidden")
coder.decodeObject(forKey: "UIBackgroundColor")
感兴趣的同学可以在项目中print试试。
网友评论