Custom UIView from Xib - requirements, good practices

时间:2018-03-25 20:08:45

标签: ios swift uiview xib

I'm trying to wrap my head around the topic of creating custom UIView using Xib files. I've done it many times, but I've never thought about why this process is so complicated, and which parts are necessary, which are good to have etc. And right now, because Xibs are the main component of the project I'm working on, I started questioning everything - I need to be sure what is happening

Assume we've just created a simple UIView subclass - let's call it CustomView, and basic requirements of this class would be to implement to required initializers lik this:

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)
    }
}

Of course we have associated Xib file, so the first thing we actually do is to go there and set the File's Owner to CustomView.

Now, the fun part begins. If you take a look at many resources available online (here, or here) everyone creates a commonInit method which is being called from both initializers and I understand it's to have a consistency between the two ways of initialization of our CustomView, but what's kinda magical is why in those methods we're loading Nib for our contentView, put constraints on it and add it as a subview to our class? Just look at the code comments:

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
    }
}

Here are the bothering questions:

  • First of all why do we have to add a contentView to our subclass?
  • Why we're loading this contentView from Xib file when it's already an @IBOutlet? Shouldn't it be given to us for free? I guess this is for the purpose of the initializing our view from code using init(frame:), right?
  • Why do we need to add this contentView as a subview and set it's constraints while Xib already specifies everything?

I've also seen a shorter version of the above code without constraints and whole loading of the first UIView from the Nib's hierarchy:

private func commonInit() {
    Bundle.main.loadNibNamed(String(describing: CustomView.self), owner: self, options: nil)

    guard let contentView = contentView else { return }
    self.addSubview(contentView)
}

How is it different from the more verbose approach and will it actually work fine with autolayout and everything?

1 个答案:

答案 0 :(得分:0)

有两种方法可以从Nib加载自定义视图。您可以在IB中添加纯UIView并将此视图设置为自定义类(类似于如何设置UITableView子类)。注意在这种情况下,文件所有者没有被修改。当您从代码加载笔尖时,您将直接获得一个完全正常工作的UIView子类。约束它并将其添加到视图层次结构是您的责任。您无法从IB本身添加此视图,因为无法从IB加载它。

第二个是将XIBs文件所有者设置为自定义类。这就是以前在Storyboard之前设置UIViewControllers的方式。文件所有者是一个代理对象,在加载时IBOutlets将连接到该代理对象。 这支持以编程方式以及从IB本身进行构造,您只需要将自定义UIView放在任何Storyboard中,并将其自定义类设置为UIView子类,并且您在自定义子类中添加的代码将加载XIB并添加其内容视图。

直接回答您的问题:

  • 意识到自定义视图已经从调用代码中创建(使用代码中的MyCustomView()或IB中的UIView对象)。因此,要保留其身份和约束,您最好的办法就是为其添加内容视图。
  • @IBOutlet只是一个占位符,指示将插座设置为此对象。实际上,加载XIB必须由您的代码完成(加载Storyboard时或由init(nibName:bundle:)构造函数在XIB中,UIViewController会为您完成)
  • XIB具有内容视图的所有内容,而不是自定义视图的所有内容。在这种方法中,XIB中的视图是普通的UIView,必须正确地限制为其父视图(自定义视图)
  • 您提到的较短版本利用translatesAutoresizingMaskIntoConstraints来基于等于父视图的框架创建约束。

编辑:第self.contentView = loadViewFromNib()行是不相关的。您无需分配给contentView(或从loadViewFromNib方法返回任何内容。您只需在XIB文件中建立连接,它将在加载时自动设置。{{1 }}参数表示您的自定义类将负责处理所有连接