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:
contentView
to our subclass? 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? 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:
init(nibName:bundle:)
constructor) 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.