简体   繁体   中英

Custom UIView from Xib - requirements, good practices

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?

There are two approaches to loading a custom view from Nib. You either add a plain UIView in IB and set this view to a custom class (similar to how a UITableView subclass is set up). Notice File Owner is not modified in this case. When you load the nib from code you get a fully working UIView subclass directly. It's your responsibility to constraint it and add it to the view hierarchy. You cannot add this view from IB itself, as you cannot load it from IB.

The second one is to set the XIBs File Owner to the custom class. This is how UIViewControllers used to be set up prior to Storyboards. The File Owner is a proxy object in which IBOutlets will be connected to when loading. This supports being constructed both programmatically and from IB itself, you only need to put a custom UIView in any Storyboard and set its custom class to you UIView subclass and the code you add in your custom subclass will load the XIB and add its content view.

Directly answering your questions:

  • Realize the custom view has already been created from the calling code (either with MyCustomView() in code or a UIView object in IB). So to preserve its identity and constraints, the best you can do is adding a content view to it.
  • The @IBOutlet is merely a placeholder that indicates outlets will be set to this object. Actually loading the XIB must be done by your code (this is done by UIViewController for you when loading a Storyboard or in a XIB using init(nibName:bundle:) constructor)
  • The XIB has everything for the content view, not the custom view. In this approach the View in the XIB is a plain UIView and must be constrained correctly to its parent (custom view)
  • The shorter version you mentioned takes advantage of translatesAutoresizingMaskIntoConstraints to create the constraints based on a frame that is equal to the parent view.

Edit: the line self.contentView = loadViewFromNib() is irrelevant. You don't need to assign to contentView (or return anything from the loadViewFromNib method. You only need to make the connection in the XIB file and it will be set automatically upon loading. The owner: self argument indicates your custom class will be responsible for handling all connections

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM