Nibs (.nib) are views Interface builder uses to design and layout views in Xcode. The name is a bit confusing because the 'N' stands for Next as in Steve Jobs old company that Apple bought, but there are represented in XCode today as .xib where the 'X' stands for XML which is how they are represented in Xcode today.
Simplest thing you can do is create a nib and then associated it with a View Controller.
- Create the nib (same name as view controller).
- Set it's File's Owner to the
ViewController
. - Point the File's Owner
view
to the nib view - Load the
ViewController
in the AppDelate like any other programatic view controller.
Create a nib Create a class. Assign the nib's File Owner to the class.
- Create a nib
- Create a class.
- Assign the nibs File Owner to the class.
Control drag the nibs view into the class.
- Load the nib.
- Add the content view.
- Note the owner is
self
.
import Foundation
import UIKit
class PaymentMethodTile: UIView {
@IBOutlet var contentView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
let bundle = Bundle.init(for: PaymentMethodTile.self)
bundle.loadNibNamed("PaymentMethodTile", owner: self, options: nil)
addSubview(contentView)
}
override var intrinsicContentSize: CGSize {
return CGSize(width: 200, height: 200)
}
}
With this method, the nib is hosting the view. Can now load in a view controller by dragging out a plain view, and assigning it’s custom class to the nib.
- Create the nib.
- Create the class.
- Set the File's Owner
- Then also set the type on the view in the nib to the custom class.
- If loading a nib programmatically, make sure you set the
IBOutlet
property to the view and not the file owner when control dragging outlets into the file. If you don't do this you will get keycode non-compliance errors.
- Then you can load that nib programmatically in any view controller like this.
import UIKit
class ViewController: UIViewController {
lazy var tile: PaymentMethodTile! = { ViewController.makePaymentMethodTile() }()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
view.addSubview(tile)
NSLayoutConstraint.activate([
tile.centerXAnchor.constraint(equalTo: view.centerXAnchor),
tile.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
static func makePaymentMethodTile() -> PaymentMethodTile? {
let bundle = Bundle(for: PaymentMethodTile.self)
let tile = bundle.loadNibNamed("PaymentMethodTile", owner: nil, options: nil)?.first as! PaymentMethodTile
tile.translatesAutoresizingMaskIntoConstraints = false
return tile
}
}
You can make a Nib appear in Interface Builder (IB) with designable attributes by doing the following. Create your new nib
- Create nib (i.e.
RedView.xib
). - Create nib view (i.e.
RedView.swift
). - Associate nib with view.
Then add it to your parent nib as a view by:
- Adding a plain
View
control to the parent - Associate the plan
View
to your newly create nib view
Create a plain old nib.
Create the view backing the nib. Make it IBDesignable
and give it an intrinsic content size to simplify Auto Layout constraints.
import UIKit
@IBDesignable
class RedView: UIView {
@IBInspectable var myColor: UIColor = .systemRed
override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = myColor
}
override var intrinsicContentSize: CGSize {
return CGSize(width: 100, height: 100)
}
}
Associate the view with the nib.
Your nib is now good to go.
To add your newly created nib to your parent, drag out a plain old View
onto your parent nib canvas. Give it some constraints (but don't worry about size).
Then associate this view with the newly created nib view created above.
This will automatically detect that it is @IBDesignable
, use it's intrinsic content size, and layout it out.
There are some gotchas with UITableViewCells
. Using the following code you can more conveniently load nibs and access their reuse identifiers as follows.
ReusableView.swift
import UIKit
protocol ReusableView: class {}
protocol NibLoadableView: class {}
extension ReusableView {
static var reuseID: String { return "\(self)" }
}
extension NibLoadableView {
static var nibName: String { return "\(self)" }
}
extension UITableViewCell: ReusableView, NibLoadableView {}
extension UICollectionViewCell: ReusableView, NibLoadableView {}
extension UITableViewHeaderFooterView: ReusableView, NibLoadableView {}
extension UITableView {
func dequeueResuableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T {
guard let cell = dequeueReusableCell(withIdentifier: T.reuseID, for: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.reuseID)")
}
return cell
}
func dequeueResuableHeaderFooter<T: UITableViewHeaderFooterView>() -> T {
guard let headerFooter = dequeueReusableHeaderFooterView(withIdentifier: T.reuseID) as? T else {
fatalError("Could not dequeue header footer view with identifier: \(T.reuseID)")
}
return headerFooter
}
func register<T: ReusableView & NibLoadableView>(_: T.Type) {
let nib = UINib(nibName: T.nibName, bundle: nil)
register(nib, forCellReuseIdentifier: T.reuseID)
}
func registerHeaderFooter<T: ReusableView & NibLoadableView>(_: T.Type) {
let nib = UINib(nibName: T.nibName, bundle: nil)
register(nib, forHeaderFooterViewReuseIdentifier: T.reuseID)
}
}
And then simple load a cell as follows.
QuickPaymentCell.swift
import UIKit
class QuickPaymentCell: UITableViewCell {
@IBOutlet var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
setupStyle()
}
func setupStyle() {
titleLabel.textColor = .reBankGrey
}
}
ParentView/ViewController
tableView.register(QuickPaymentCell.self) // Note: No cell resuseIdentifier used
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: QuickPaymentCell = tableView.dequeueResuableCell(for: indexPath)
cell.titleLabel.text = games[indexPath.row]
return cell
}
This method is key to triggering awakeFromNib
but is nice because it saves a tonne of code.