Xib Awakening: A Uniform Way to Load All Xibs
Yoseob Lee
Yoseob Lee

Xib Awakening: A Uniform Way to Load All Xibs

Yoseob Lee, iOS Engineer

In any development project, reusability is the name of the game. It speeds up the development process and reduces the amount of bugs* since you are not writing new code. And, in the realm of reusable UI components, xibs are the go-to approach.

But what exactly is a xib or nib file? In conversation both words tend to be used rather interchangeably and that’s okay – we’ll get into that shortly! We’ll explore the differences between xibs and nibs and a standardized approach for working with them.

Xibs are essentially self-contained bits of the user interface that are designed via the Interface Builder. Imagine that you are asked to create a cluster of buttons in a 4×4 grid and that this “view” would be reused in multiple parts of the app. How much easier would it be to create just one view that you could use for every single instance? That’s the power of xibs.


*Provided you’ve thoroughly tested the parts you are reusing.

Xib’s Anatomy

Interface Builder → Xib → Build → Nib

Xibs and nibs are pretty much the same thing; the real difference being their role in the overall process.

Nibs, known as NeXT Interface Builder files, are actually compiled from xib files and act as a “bundle” that contain all of its relevant files. This is what old-school iOS developers had to work with, but, for most of us, you can think of nibs as the final product in the lifecycle of a custom view.

Xibs, known as XML Interface Builder files, are what most of us are familiar with. Create a new user-interface file on Xcode and take a look at the extension — xib! Whenever you are working on a custom interface in Xcode, you are working in a xib. Xibs were introduced in Xcode 3.0 as a way of addressing the common issue of version control associated with nibs. Since nibs were actually bundles, it made version control much more difficult; hence xib files are essentially “flat” XML files (think about any Storyboard conflicts — those are also XML files) that provide more clarity on changes between versions.

The File Owner is the “controller” that handles the contents of the xib file. Consider who will be owning the IBOutlets for the view. For our purposes, I will not be going into depth on the First Responder, but suffice to say it is your gateway into the Responder Chain.

UIView Lifecycle

There are a million things that happen behind the scenes to load the xib you see on your screen to the actual UI on your phone, but these three are hooks a developer would most likely interact with:

required init?(coder aDecoder: NSCoder) {
// During initialization (IB Object)
}
override init(frame: CGRect) {
	// During initialization (Programmatic)
}
override awakeFromNib() {
	// After initialization (all Outlets will be connected)
}

Our Approach

Remember that scenario I asked you to imagine earlier about the 4×4 grid? Those were pretty much the requirements I had when I was tackling a feature on Motel 6. It was around that time I realized I actually had no idea how to load a xib file via Storyboard. The rest of the project we had simply been instantiating our custom views programmatically and adding our new instance as a subview. That setup code had been written once and forgotten about…“if it ain’t broke, don’t fix it” indeed.

In addition to our approach not being able to handle instantiation via Interface Builder, it was also incredibly hefty with multiple layers of protocols (rarely would I advocate having less protocols but in this case something had to go).

To give you an idea, below is an example implementation of our logic used to instantiate custom views:

protocol Identifiable {
	static var identifier: String { get }
}
extension Identifiable {
	static var identifier: String {
		return String(describing: self)
	}
}
protocol NibLoadable: Identifiable {
	static var nibName: String { get }
}
extension NibLoadable {
	@nonobjc static var nibName: String {
		Return String(describing: self)
	}
}
extension UIView {
	static func loadFromNib<T: NibLoadable>(ofType _: T.Type) -> T {
		guard let nibViews = Bundle.main.loadNibNamed(T.nibName, owner: nil, options: nil) else {
			fatalError(“Could not instantiate view from nib file.”)
		}
		
		for view in nibViews {
			If let typedView = view as? T {
				return typedView
			}
		}
		fatalError(“Could not instantiate view from nib file.”)
	}
}

To be fair, this works, but in its current form can really only effectively handle programmatic instantiation.

One Way to Rule Them All

Showing has always been stronger than telling, and what better way to demonstrate how something works than by actually using it?

Usage

1. Create custom user-interface view file (name it CustomView)

2. Create a custom Swift file matching the name of the xib file

3. Set File Owner to be your custom class

4. Implement init methods, with a call to xibSetup()

5. Add a UIView in your UIViewController (located inside your Storyboard)

6. Set its class to CustomView

7. That’s it!

 

Below is the full image of the setup including the view controller and storyboard file:

Building the simulator should present you with this wonderful 4×4 view!

The Approach

Be it programmatically or via Interface Builder, our new approach provides one simple method to handle instantiation from any path.

 

 

extension UIView {
1. Call this method from your init functions
/// Helper method to init and setup the view from the Nib.
func xibSetup() {
	let view = loadFromNib()
	addSubview(view)
	stretch(view: view)
}
 
	2. Loads the view from the nib in the bundle
	/// Method to init the view from a Nib.
	///
	/// - Returns: Optional UIView initialized from the Nib of the same class name.
func loadFromNib<T: UIView>() -> T {
	let selfType = type(of: self)
	let bundle = Bundle(for: selfType)
	let nibName = String(describing: selfType)
	let nib = UINib(nibName: nibName, bundle: bundle)
 
	guard let view = nib.instantiate(withOwner: self, options: nil).first as? T else {
		fatalError(“Error loading nib with name \(nibName))”
}
 
return view
}
 
3. Resizes the loaded view, ready for use
/// Stretches the input view to the UIView frame using Auto-layout
///
/// - Parameter view: The view to stretch.
func stretch(view: UIView) {
	view.translatesAutoresizingMaskIntoConstraints = false
 
	NSLayoutConstraint.active([
		view.topAnchor.constraint(equalTo: topAnchor),
		view.leftAnchor.constraint(equalTo: leftAnchor),
		view.rightAnchor.constraint(equalTo: rightAnchor),
		view.bottomAnchor.constraint(equalTo: bottomAnchor)
		])
}
}

Before we dig into what each component does, I feel it necessary to establish a distinction between the “view” which we will call the “loaded view”  in the xibSetup() function and the “view” (“custom view”) that is actually calling this function.

The custom view, in our case, is the corresponding Swift file associated with our nib.

The loaded view is the representation of the user interface that is unarchived from the nib.

1. The access way into loading the xib. Simply call this function within your init functions.

2. Piggybacking on the UIView’s built-in functionality of addSubview(:), we load the nib file (which represents the user interface) and add it as a subview.

3. Stretch the loaded view to be the same size as the “custom view.”

Caveats

If you noticed in our approach, there is a distinct lack of protocols. That was a conscious choice since ordinarily whenever you develop a custom view, the name of the nib would match the corresponding Swift file (since they both refer to the same object).

However, that said, if for some reason your file names do not match, you would have to abstract the nibName behind a protocol (like Identifiable) in order to load the correct nib.

Conclusion

Our new approach has definitely streamlined the development process of creating reusable views for a number of reasons.

  1. “Set and forget” after the initial setup; the code is ready for immediate use.
  2. Simpler development. You can create any number of reusable views with as many nested custom views and only have to worry about calling xibSetup() in their respective init functions (In the example up above, each square is actually another UIView subclass that is loaded by the CustomView).

However, there is one drawback to our approach. Because the process is automatic from the moment you call xibSetup() you cannot handle dependency injection in custom views. That isn’t to say dependency injection is not possible — you’d simply have to create custom methods that can inject after instantiation.

Hopefully after reading this article, and armed with the power of nibs, you too can venture forth and create amazing reusable views!