Teabyte

Mobile app development for iOS and Swift

Custom .xib component for iOS

2019-06-28

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.

Define File's Owner class

Now it is even possible to create outlets from the .xib to our file's owner class.

Create outlets

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.