我正在尝试用XIB文件创建 custom UIView
的主题。我已经做了很多次了,但是我从来没有想过为什么这个过程如此复杂,哪些部分是必要的,哪个是好的等。现在,因为xib是我正在从事的项目的主要组成部分,我开始质疑所有内容 - 我需要确定发生了什么😉
假设我们刚刚创建了一个简单的UIView
子类 - 让我们称其为CustomView
,此类的基本要求是实现对所需的初始化器喜欢以下内容:
class CustomView: UIView {
// This one is for initializing programmatically
override init(frame: CGRect) {
super.init(frame: frame)
}
// This one is for Storyboards and Xibs
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
当然,我们已经关联了XIB文件,因此我们实际要做的第一件事是去那里,将文件的所有者设置为 CustomView
。
现在,有趣的部分开始了。如果您查看在线上可用的许多资源(在这里或这里(,每个人都会创建一种commonInit
方法,这两个方法都从初始化器中调用,我知道这是我们CustomView
初始化的两种初始化方法之间的一致性,但是什么是 Magical 是为什么在这些方法中,我们正在为contentView
加载NIB,对其进行约束并将其添加为我们的类的子视图?只需查看代码注释:
class CustomView: UIView {
@IBOutlet weak var contentView: UIView?
@IBOutlet weak var someSubview: UIView?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
// Why we're loading this `contentView` from Xib?`
self.contentView = loadViewFromNib()
// Why do we have to add it when I's already added in IB?
self.addSubview(contentView)
// Why do we need a constraints for it when IB shows it's filling whole view?
self.contentView.translatesAutoresizingMaskIntoConstraints = false
let trailingAnchor = contentView.trailingAnchor.constraint(equalTo: trailingAnchor)
let leadingAnchor = contentView.leadingAnchor.constraint(equalTo: leadingAnchor)
let topAnchor = contentView.topAnchor.constraint(equalTo: topAnchor)
let bottomAnchor = contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
NSLayoutConstraint.activate([trailingAnchor, leadingAnchor, topAnchor, bottomAnchor])
}
private func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView
return nibView
}
}
这是困扰的问题:
- 首先,为什么我们必须在我们的子类中添加
contentView
? - 为什么我们已经是
@IBOutlet
时从XIB文件中加载此contentView
?不应该免费给我们吗?我想这是出于使用init(frame:)
从代码初始化我们的视图的目的,对吗? - 为什么我们需要添加此
contentView
作为子视图并设置其约束,而XIB已经指定了所有内容?
我还看到了上述代码的 shorter 版本,而没有约束,并且从NIB的层次结构中进行了第一个UIView
的整体加载:
private func commonInit() {
Bundle.main.loadNibNamed(String(describing: CustomView.self), owner: self, options: nil)
guard let contentView = contentView else { return }
self.addSubview(contentView)
}
它与更多的详细方法有何不同,它实际上可以与AutoLayout和所有内容一起工作?
有两种方法可以从NIB加载自定义视图。您要么在IB中添加一个普通的UIVIEW,然后将此视图设置为自定义类(类似于如何设置Uitable View子类(。在这种情况下,请注意文件所有者未修改。从代码加载NIB时,您会直接获得一个完全工作的Uiview子类。限制它并将其添加到视图层次结构是您的责任。您无法从IB本身添加此视图,因为您无法从IB中加载它。
第二个是将XIBS文件所有者设置为自定义类。这就是在故事板之前以前设置的UiviewControllers的方式。文件所有者是一个代理对象,加载时将连接IBOUTLETS。这支持以编程方式构建和从IB本身构造,您只需要在任何故事板中放置自定义Uiview,并将其自定义类设置为UIView子类,并且您在自定义子类中添加的代码会加载XIB并添加其内容视图。
直接回答您的问题:
- 意识到自定义视图已经是从调用代码创建的(在代码中使用mycustomview((或IB中的Uiview对象(。因此,为了保留其身份和约束,您能做的最好的就是添加内容视图。
- @iboutlet只是一个占位符,指示出口将设置为此对象。实际上,加载XIB必须由您的代码完成(这是由UiviewController使用
init(nibName:bundle:)
构造函数在加载故事板或XIB时为您完成的( - XIB具有内容视图的所有内容,而不是自定义视图。在这种方法中
- 您提到的较短版本利用
translatesAutoresizingMaskIntoConstraints
的优势基于与父视图相等的帧创建约束。
编辑:线self.contentView = loadViewFromNib()
是无关紧要的。您无需分配给contentView
(或从loadViewFromNib
方法返回任何内容。您只需要在XIB文件中建立连接,并且将在加载时自动设置该连接。owner: self
参数表明您的自定义类将负责处理所有连接