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
- Make nib free form
- Drag outlets to view (not
File's Owner
). - Watch out for
SafeAreaLayoutGuide
when setting constraints
- Add this code to the class
import UIKit
class FooView: UIView {
@IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
let bundle = Bundle(for: FooView.self)
bundle.loadNibNamed(String(describing: FooView), owner: self, options: nil)
addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
}
}
Set the nib Fileowner
to the class.
- Add the content view.
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.
Original nib with safe area and nonsafe area labels.
If you don't pin the inner contentView
the nib won't work with auto layout. It will use its native nib size to show on screen. And you don't be able to lay it out nicely with others.
Notice how when it layed out the nib it smushed the safe and non-safe labels together. Just be mindful when doing layout whether you are pinning to a safe or non-safe area.
It may not matter, but you will be confused if you are counting in certain behavior and may find it is not there
- Create the nib.
- Create the class.
- Set the File's Owner
- Set the Custom Class on the View
- Load in view controller programmatically
Create the nib and the class.
- Then also set the type on the view in the nib to the custom class.
When creating the class don't use a contentView. Instead load like this.
class PaymentMethodTile: UIView {
@IBOutlet var headerLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
}
Set the Custom Class
on the View
in the nib too.
When dragging outlets out into your nib, 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.
Programmatically load in 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.
Create the nib as a UITableViewCell
:
Set its FileOwner
:
Set its Classname
:
Create the class:
import UIKit
class CRAAccountSelectorCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
setupStyle()
}
func setupStyle() {
}
}
Remember when dragging in outlets to set them to the parent view
NOT the File's Owner
:
Import the following reuse helper:
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)
}
}
Use it in a view controller like this:
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
}
Load it via the bundle like this - will load and setup all the outlets.
let bundle = Bundle(for: ReceivedTableCell.self)
cell = bundle.loadNibNamed("ReceivedTableCell", owner: nil, options: nil)?.first as! ReceivedTableCell
Create a regular view.
Create a class.
import UIKit
class TransactionsSectionSearchFooterView: UITableViewHeaderFooterView {
}
Don't set File's Owner
.
Set Custom Class
:
Register with tableView
:
tableView.registerHeaderFooter(TransactionsSectionSearchFooterView.self)
Dequeue and use as before with helper methods:
let footerView: TransactionsSectionSearchFooterView = tableView.dequeueResuableHeaderFooter()
Both these work. Not sure which one is better.
override func awakeFromNib() {
super.awakeFromNib()
let bundle = Bundle(for: BadgeLabel.self)
bundle.loadNibNamed(String(describing: BadgeLabel.self), owner: self, options: nil)
addSubview(contentView)
}
override func awakeFromNib() {
super.awakeFromNib()
let bundle = Bundle.init(for: BadgeLabel.self)
bundle.loadNibNamed("BadgeLabel", owner: self, options: nil)
addSubview(contentView)
}
How you size your nib in interface builder matters. If you give your nib a frame size of 113x115, that is how it is going to draw it on screen (regardless of auto layout).
If you want your nib to appear the right size, you need to draw it to the size your want (i.e. 48x48).