Even with the recently announced SwiftUI framework Storyboards and xib files aren't going anywhere for at least the next one to two years. In roughly every iOS/macOS application development there is a need need for a custom component view. One of my awesome co-workers, Björn (check out his work! : https://www.bjoernacker.de), showed me an excellent and simple way of implementing such custom view component using .xib
files. Those files contain separated view definitions which can be loaded and used throughout the application. To make our lives easier, Swift protocols offer a great way to load such files.
First create a new file called XibComponent.swift
:
import Foundation
import UIKit
protocol XibComponent where Self: UIView {
func setup()
}
Define a protocol which can be adopted by every type which is a UIView
. It will also require every type to implement a setup()
method.
Extend the protocol by providing a default implementation of a loadViewFromNib()
method.
extension XibComponent {
func loadViewFromNib(type: AnyClass) {
let bundle = Bundle(for: type)
let nib = UINib(nibName: String(describing: type), bundle: bundle)
let views = nib.instantiate(withOwner: self, options: nil)
if let view = views.last as? UIView {
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
}
}
loadViewFromNib()
takes a reference for a class, extracts the class name and searches for a .xib
file which name matches the name of the class. After that it instantiates the .xib
file and adds it as a subview to the current view. Furthermore it will adjust the loaded .xib
bounds the currently available space.
Use the protocol
The protocol can be easily used to load and define custom views. Let's take a very simple example. Create a new class inheriting from UIView
called LabelView
. Make it conform to the XibComponent
protocol.
import UIKit
class LabelView: UIView, XibComponent {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
loadViewFromNib(type: LabelView.self)
}
}
Within the setup()
the loadViewFromNib()
method attempts to load a .xib
file with the same name as the class, "LabelView". Since this file does not yet exist, create a new View
file and call it LabelView.xib
.
Define the File's owner as the newly created LabelView
class. This is important for everything to work.
Now it is even possible to create outlets from the .xib
to our file's owner class.
Modify the label's text value to see if the code is actually executed.
import UIKit
class LabelView: UIView, XibComponent {
@IBOutlet weak var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
loadViewFromNib(type: LabelView.self)
label.text = "Hello from Xib"
}
}
Now you can either instantiate the component via code or drag and drop it on the interface designer.