Custom UIView subclass from a xib file
Do you want to learn how to load a xib file to create a custom view object? Well, this UIKit tutorial is just for you written in Swift.
I already have a comprehensive guide about initializing views and controllers, but that one lacks a very special case: creating a custom view using interface builder. đ€·ââïž
Loading xib files
Using the contents of a xib file is a pretty damn easy task to do. You can use the following two methods to load the contents (aka. the view hierarchy) of the file.
let view = UINib(
nibName: "CustomView",
bundle: .main
).instantiate(
withOwner: nil,
options: nil
).first as! UIView
// does the same as above
// let view = Bundle.main.loadNibNamed(
// "CustomView",
// owner: nil,
// options: nil
// )!.first as! UIView
view.frame = self.view.bounds
self.view.addSubview(view)
The snippet above will simply instantiate a view object from the xib file. You can have multiple root objects in the view hierarchy, but this time letâs just pick the first one and use that. I assume that in 99% of the cases this is what youâll need in order to get your custom designed views. Also you can extend the UIView object with any of the solutions above to create a generic view loader. More on that later⊠đ
This method is pretty simple and cheap, however there is one little drawback. You canât get named pointers (outlets) for the views, but only for the root object. If you are putting design elements into your screen, thatâs fine, but if you need to display dynamic data, you might want to reach out for the underlying views as well. đ
Custom views with outlets & actions
So the proper way to load custom views from xib files goes something like this:
Inside your custom view object, you instantiate the xib file exactly the same way as I told you right up here. đ The only difference is that you donât need to use the object array returned by the methods, but you have to connect your view objects through the interface builder, using the Fileâs Owner as a reference point, plus a custom container view outlet, thatâll contain everything you need. đ€š
// note: view object is from my previous tutorial, with autoresizing masks disabled
class CustomView: View {
// this is going to be our container object
@IBOutlet weak var containerView: UIView!
// other usual outlets
@IBOutlet weak var textLabel: UILabel!
override func initialize() {
super.initialize()
// first: load the view hierarchy to get proper outlets
let name = String(describing: type(of: self))
let nib = UINib(nibName: name, bundle: .main)
nib.instantiate(withOwner: self, options: nil)
// next: append the container to our view
self.addSubview(self.containerView)
self.containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.containerView.topAnchor.constraint(equalTo: self.topAnchor),
self.containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
])
}
}
So the initialize method here is just loading the nib file with the owner of self. After the loading process finished, your outlet pointers are going to be filled with proper values from the xib file. There is one last thing that we need to do. Even the views from the xib file are âprogrammaticallyâ connected to our custom view object, but visually they arenât. So we have to add our container view into the view hierarchy. đ€
If you want to use your custom view object, you just have to create a new instance from it - inside a view controller - and finally feel free to add it as a subview!
One word about bounds, frames aka. springs and struts: fucking UGLY! Thatâs two words. They are considered as a bad practice, so please use auto layout, I have a nice tutorial about anchors, they are amazing and learning them takes about 15 minutes. đ
class ViewController: UIViewController {
weak var customView: CustomView!
override func loadView() {
super.loadView()
let customView = CustomView()
self.view.addSubview(customView)
NSLayoutConstraint.activate([
customView.topAnchor.constraint(equalTo: self.view.topAnchor),
customView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
customView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
customView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
self.customView = customView
}
override func viewDidLoad() {
super.viewDidLoad()
self.customView.textLabel.text = "Lorem ipsum"
}
}
Thatâs it, now you have a completely working custom UIView object that loads a xib file in order to use itâs contents. Wasnât so bad, right? đ€Ș
One more extra thing. If you donât like to handle views programmatically or you simply donât want to mess around with the loadView
method, just remove it entirely. Next put the @IBOutlet
keyword right before your custom view class variable. Open your storyboard using IB, then drag & drop a new UIView element to your controller and connect the custom view outlet. It should work like magic. đ«
I promised outlets and actions in the heading of this section, so letâs talk a little bit about IBActions. They work exactly the same as youâd expect them with controllers. You can simply hook-up a button to your custom view and delegate the action to the custom view class. If you want to forward touches or specific actions to a controller, you should use the delegate pattern or go with a simple block. đ
Ownership and container views
It is possible to leave out all the xib loading mechanism from the view instance. We can create a set of extensions in order to have a nice view loader with a custom view class from a xib file. This way you donât need a container view anymore, also the owner of the file can be left out from the game, itâs more or less the same method as reusable cells for tables and collections created by Apple. đ
You should know that going this way you canât use your default UIView init methods programmatically anymore, because the xib file will take care of the init process. Also if you are trying to use this kind of custom views from a storyboard or xib file, you wonât be able to use your outlets, because the correspondig xib of the view class wonât be loaded. Otherwise if you are trying to load it manyally youâll run into an infinite loop and eventually your app will crash like hell. đ
import UIKit
extension UINib {
func instantiate() -> Any? {
return self.instantiate(withOwner: nil, options: nil).first
}
}
extension UIView {
static var nib: UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
static func instantiate(autolayout: Bool = true) -> Self {
// generic helper function
func instantiateUsingNib<T: UIView>(autolayout: Bool) -> T {
let view = self.nib.instantiate() as! T
view.translatesAutoresizingMaskIntoConstraints = !autolayout
return view
}
return instantiateUsingNib(autolayout: autolayout)
}
}
class CustomView: UIView {
@IBOutlet weak var textLabel: UILabel!
}
// usage (inside a view controller for example)
// let view = CustomView.instantiate()
Just like with table or collection view cells this time you have to set your custom view class on the view object, instead of the Fileâs Owner. You have to connect your outlets and basically youâre done with everything. đ€
From now on you should ALWAYS use the instantiate method on your custom view object. The good news is that the function is generic, returns the proper instance type and itâs highly reusable. Oh, btw. I already mentioned the bad news⊠đ€Ș
There is also one more technique by overriding awakeAfter, but I would not rely on that solution anymore. In most of the cases you can simply set the Fileâs Owner to your custom view, and go with a container, thatâs a safe bet. If you have special needs you might need the second approach, but please be careful with that. đ
Related posts
10 little UIKit tips you should know
In this article I've gathered my top 10 favorite modern UIKit tips that I'd definitely want to know before I start my next project.
Building input forms for iOS apps
Learn how to build complex forms with my updated collection view view-model framework without the struggle using Swift.
Custom views, input forms and mistakes
Just a little advice about creating custom view programmatically and the truth about why form building with collection views sucks.
How to create reusable views for modern collection views?
A quick intro to modern collection views using compositional layout, diffable data source and reusable view components.