XIB的自定义Uiview-要求,良好实践



我正在尝试用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参数表明您的自定义类将负责处理所有连接

最新更新