Chapter 7. Nib Files

A view (UIView) is an interface object, which draws itself into a rectangular area. Your app’s visible interface consists of views. When your app launches, some view controller is made the root view controller of your app’s window (“How an App Gets Going”). That view controller has a main view. That view and its subviews now occupy the window. Whatever that view and its subviews look like when they draw themselves, that is what the user will see.

Where do these interface views come from? Well, UIView is a class; an individual UIView is an instance of that class. And you know how to make an instance of a class — you call that class’s initializer:

let v = UIView()

So you could create all your interface views in code, one by one. For each view, you would instantiate it; then you would configure it. You’d say where it should go on the screen, what size it should have, what color it should be. If the view is a button or a label, you’d say what text it should display. And so on.

But that could be a lot of code. Wouldn’t it be nice if, instead, you could draw your desired interface, just as in a drawing application, and have the runtime build the interface for you, based on your drawing? Well, you can — using a nib.

A nib is a file, in a special format, consisting of instructions for creating and configuring instances — primarily UIView instances. You “write” those instructions graphically, by drawing. You design your app’s interface visually. Under the hood, Xcode encodes that design. When the app runs and it’s time for those UIView instances to appear visibly to the user, the runtime loads the nib. It decodes the instructions and obeys them: it actually creates and configures the view instances that the nib describes.

So that you can draw your interface into a nib file, Xcode includes a graphical design environment, which I call the nib editor. Long ago, the files on which the nib editor operated were literally nib files — that is, they had a .nib file extension. Nowadays, you’ll use the nib editor to edit a .storyboard file or a .xib file. However, they will be turned into actual .nib files when you build your project (“Nib Files”), so I still refer to them loosely as nibs or nib files.

Note

The name nib has nothing to do with fountain pens or bits of chocolate. The nib editor used to be a separate application called Interface Builder. The operating system for which it was originally developed was called NeXTStep. The files created by Interface Builder were given the .nib file extension as an acronym for “NeXTStep Interface Builder.”

You don’t have to use nibs to create your interface objects. The loading of a nib does nothing that you could not have done directly, in code. A nib is just a device for making the creation of interface views convenient and compact; one nib file can generate many views, and the nib editor’s visual representation of those views can be more intuitive than a code description. But you still have the alternative of using code: you can programmatically instantiate a UIView, you can configure it, you can place that view into your interface — manually, step by step, one line of code at a time. You are free to mix and match; you can generate some views in code and design other views in the nib editor, or design a view in the nib editor but complete its configuration in code.

Clearly, nibs can be a powerful and convenient way to create your app’s interface, in whole or in part. Beginners often use nibs, to take advantage of that power and convenience, without knowing what they really are, how they really work, or how to manipulate them in code — as if nibs were some kind of impenetrable magic. But nibs are not magic, and they are not hard to understand. Failure to understand nibs and nib loading opens the door to some elementary, confusing mistakes that can be avoided or corrected merely by grasping the basic facts outlined in this chapter.

Tip

One of the salient features of SwiftUI is that it avoids nibs. By condensing the code needed to create views programmatically, it lets you describe your interface clearly and compactly in Swift. Part of the goal is multiplatform reusability: the same code can construct an interface destined for an Apple Watch, an iPhone, an Apple TV, or a desktop Mac.

The Nib Editor Interface

Let’s explore Xcode’s nib editor. This is where you’ll draw your app’s interface graphically. In Chapter 6, we created a simple project, Empty Window, directly from the Single View App template; it contains a storyboard file, so we’ll use that. In Xcode, open the Empty Window project, locate Main.storyboard in the Project navigator, and click to edit it.

ios13 0701numbered
Figure 7-1. Editing a nib file

Figure 7-1 shows part of the project window after selecting Main.storyboard in the Project navigator. The interface may be considered in four pieces:

  1. The bulk of the editor is devoted to the canvas, where you physically design your app’s interface. The canvas portrays views graphically. View controllers are also represented in the canvas; a view controller isn’t a view, so it isn’t drawn in your app’s interface, but it has a view, which is drawn.

  2. At the left of the editor is the document outline, listing the storyboard’s contents hierarchically by name. It can be hidden by dragging its right edge or by clicking the button at the bottom left corner below the canvas.

  3. To the right of the editor, the inspectors in the Utilities pane let you edit details of the currently selected object.

  4. The Objects Library, available as a floating window (View → Show Library, Command-Shift-L), is your source of interface objects to be added to the canvas or the document outline.

Document Outline

The document outline portrays hierarchically the relationships between the objects in the nib. This structure differs slightly depending on whether you’re editing a .storyboard file or a .xib file.

In a storyboard file, the primary constituents are scenes. A scene is, roughly speaking, a single view controller, along with some ancillary material; every scene has a single view controller at its top level.

A view controller isn’t an interface object, but it manages an interface object, a view that serves as its main view. A scene’s view controller is displayed in the canvas with its main view inside it. In Figure 7-1, the large rectangle in the canvas is a view controller’s main view, and is actually inside a view controller. The view controller itself can be seen and selected in the document outline. It is also represented as an icon in the scene dock, which appears above the view controller in the canvas when anything in this scene is selected (Figure 7-2).

In the document outline, all the scenes are listed. Each scene is the top level of a hierarchical list. Hierarchically down from each scene are the objects that also appear in the view controller’s scene dock: the view controller itself, along with two proxy objects, the First Responder token and the Exit token. They are the scene’s top-level objects. The view controller’s main view appears hierarchically down from the view controller, and the main view’s subviews appear hierarchically down from that, reflecting the interface hierarchy of superviews and subviews.

ios13 0702
Figure 7-2. A view controller selected in a storyboard

Objects listed in the document outline are of two kinds:

Nib objects

The view controller, along with its main view and its subviews, will be turned into actual instances when the nib is loaded by the running app. They are called nib objects.

Proxy objects

Proxy objects (here, the First Responder and Exit tokens) will not be turned into instances when the nib is loaded. They represent objects that already exist. They are displayed in the nib in order to facilitate communication between nib objects and those already existing objects; I’ll give examples later in this chapter.

The document outline also contains the Storyboard Entry Point. This isn’t an object of any kind. It’s an indicator that this view controller is the storyboard’s initial view controller (because Is Initial View Controller is checked in the view controller’s Attributes inspector); it corresponds to the right-pointing arrow seen at the left of this view controller in the canvas in Figure 7-1.

In a .xib file, there are no scenes. What would be, in a .storyboard file, the top-level objects of a scene become, in a .xib file, the top-level objects of the nib itself; and the top-level interface object of a .xib file is usually a view. (A .xib file can contain a view controller, but it usually doesn’t.) A .xib file’s top-level view might well be a view that is to serve as a view controller’s main view, but that’s not a requirement. Figure 7-3 shows a .xib file with a structure parallel to the single scene of Figure 7-2.

ios13 0703
Figure 7-3. A .xib file containing a view

The document outline in Figure 7-3 lists three top-level objects. Two of them are proxy objects, termed Placeholders in the document outline: the File’s Owner, and the First Responder. The third is a nib object, a view; it will be turned into a UIView instance when the nib is loaded as the app runs.

At present, the document outline may seem unnecessary, because there is very little hierarchy; all objects in Figures 7-2 and 7-3 are readily accessible in the canvas. But when a storyboard contains many scenes, and when a view contains many levels of hierarchically arranged objects, some of which are difficult to see or select in the canvas, you’re going to be very glad of the document outline, which lets you survey the contents of the nib in a nice hierarchical structure, and where you can locate and select the object you’re after. You can also rearrange the hierarchy here; if you’ve made a view a subview of the wrong superview, you can reposition it within this outline by dragging its name.

Tip

You can also select objects using the jump bar at the top of the editor: the last jump bar path component is a hierarchical pop-up menu similar to the document outline.

If the names of nib objects in the document outline seem generic and uninformative, you can change them. The name is technically a label, and has no special meaning, so feel free to assign nib objects labels that are useful to you. Select a nib object’s label in the document outline and press Return to make it editable, or select the object and edit the Label field in the Document section of the Identity inspector.

Canvas

The canvas provides a graphical representation of a view and its subviews, similar to what you’re probably accustomed to in any drawing program. The canvas is scrollable and automatically accommodates however many graphical representations it contains, and can also be zoomed (Option-scroll, or choose Editor → Zoom, or use the contextual menu or the zoom buttons at the bottom of the canvas).

Our simple Empty Window project’s Main.storyboard contains just one scene, so the only thing it represents in the canvas is that scene’s view controller with its main view inside it. When the app runs, this view controller will become the window’s rootViewController; therefore its view will occupy the entire window, and will effectively be our app’s initial interface. That gives us an excellent opportunity to experiment: any visible changes we make within this view should be visible when we subsequently build and run the app! To prove it, let’s add a subview:

  1. Start with the nib editor looking more or less like Figure 7-1.

  2. Summon the Objects Library (Command-Shift-L, or click the Library button in the project window toolbar). Make sure it’s displaying objects (not images or colors). If it’s in icon view (a grid of icons without text), switch to list view. Click in the search field and type “button” so that only button objects are shown in the list. The Button object we’re after is listed first.

  3. Drag the Button object from the Library into the view controller’s main view in the canvas (Figure 7-4), and let go of the mouse.

ios13 0705
Figure 7-4. Dragging a button into a view

A button is now present in the view in the canvas. The move we’ve just performed — dragging from the Library into the canvas — is extremely characteristic; you’ll do it often as you design your interface.

Tip

By default, the Library floating window is temporary; it vanishes as soon as you drag something out of it. To make it remain onscreen after the drag, hold Option when summoning the Library or when dragging out of it; the Library window’s toolbar then contains a close button, and the window will remain open until you click that button.

Much as in a drawing program, the nib editor provides features to aid you in designing your interface. Here are some things to try:

  • Select the button: resizing handles appear.

  • Using the resizing handles, resize the button to make it wider: dimension information appears.

  • Drag the button near an edge of the view: a guideline appears, showing standard spacing. Similarly, drag the button near the center of the view: a guideline shows you when the button is centered.

  • With the button selected, hold Option (but not the mouse button) and hover the mouse outside the button: arrows and numbers appear showing the distance between the button and the edges of the view.

  • Control-Shift-click the button: a menu appears, letting you select the button or whatever is behind it or up the hierarchy from it. This is useful particularly when views overlap.

  • Double-click the button’s title. The title becomes editable. Give it a new title, such as “Hello.” Press Return to set the new title.

To prove that we really are designing our app’s interface, we’ll run the app:

  1. Drag the button to a position near the top left corner of the canvas. (If you don’t do this, the button could be off the screen when the app runs.)

  2. Examine the Debug → Activate / Deactivate Breakpoints menu item. If it says Deactivate Breakpoints, choose it; we don’t want to pause at any breakpoints you may have created while reading the previous chapter.

  3. Make sure the destination in the Scheme pop-up menu is an iPhone simulator.

  4. Choose Product → Run (or click the Run button in the toolbar).

After a heart-stopping pause, the Simulator opens, and presto, our empty window is empty no longer (Figure 7-5); it contains a button! You can tap this button with the mouse, emulating what the user would do with a finger; the button highlights as you tap it.

ios13 0706
Figure 7-5. The Empty Window app’s window is empty no longer

Inspectors

In addition to the File, History, and Quick Help inspectors, four inspectors appear in conjunction with the nib editor, and apply to whatever object is selected in the document outline, dock, or canvas:

Identity inspector (Command-Option-4)

The first section of this inspector, Custom Class, is the most important. Here you can learn — and can change — the selected object’s class.

Attributes inspector (Command-Option-5)

Settings here correspond to properties and methods that you might use to configure the object in code. Selecting our view and choosing from the Background pop-up menu in the Attributes inspector corresponds to setting the view’s backgroundColor property in code.

The Attributes inspector has sections corresponding to the selected object’s class inheritance. The UIButton Attributes inspector has three sections: in addition to a Button section, there’s a Control section (because a UIButton is also a UIControl) and a View section (because a UIControl is also a UIView).

Size inspector (Command-Option-6)

The X, Y, Width, and Height fields determine the object’s position and size within its superview, corresponding to its frame property in code; you can equally set these values in the canvas by dragging and resizing, but numeric precision can be desirable.

Connections inspector (Command-Option-7)

I’ll demonstrate use of the Connections inspector later in this chapter.

Loading a Nib

A nib file is a collection of potential instances — its nib objects. They become actual instances only if, while your app is running, the nib is loaded. At that moment, the nib objects described in the nib are effectively transformed into instances that are available to your app. I call this process — the loading of a nib and the resulting creation of instances — the nib-loading mechanism. We may speak as if a nib contains literal object instances; we may speak of “loading” or “instantiating” a view controller or a view from a nib. But in fact the nib contains nothing but instructions for creating a view controller or view instance, and the nib-loading mechanism fulfills those instructions.

Using nibs as a source of instances is efficient. Interface is relatively heavyweight stuff, but a nib is small. Moreover, a nib isn’t loaded until it is needed; indeed, it might never be loaded. So this heavyweight stuff won’t come into existence until and unless it is about to be displayed.

There’s no such thing as “unloading” a nib. A nib is loaded, its nib objects are turned into instances, those instances are handed over to the running app, and that’s all; the nib has done its job. It is then up to the running app to decide what to do with the instances that just sprang to life. It must hang on to them for as long as it needs them, and will let them go out of existence when they are no longer needed. Typically, it will do this by adding them to the interface, where they will persist until that interface as a whole is removed.

The same nib file can be loaded multiple times, generating a new set of instances each time. A nib is thus a mechanism for reproducing a view controller or a view hierarchy as many times as necessary. A nib file might represent interface that you intend to use in several places in your app — possibly several places simultaneously. A case in point is the repeated cells in a table view.

Loading a View Controller Nib

A nib containing a view controller will almost certainly come from a storyboard. (A .xib file can contain a view controller, but it usually won’t.) A storyboard is a collection of scenes. Each scene starts with a view controller. Each view controller in a storyboard ends up in an individual nib. When that view controller is needed, that nib is loaded.

A view controller may be loaded from a storyboard automatically (by the runtime) or manually (by your code):

Automatic creation of a view controller

There are two main occasions when a view controller is loaded automatically from a nib:

At app launch time

As your app launches, if it has a main storyboard, the runtime looks for that storyboard’s initial view controller (entry point) and loads its nib, turning it into a view controller instance to serve as the app’s root view controller (“How an App Gets Going”).

When a segue is performed

A storyboard typically contains several scenes connected by segues; when a segue is performed, the destination scene’s view controller nib is loaded and turned into an instance.

Manual instantiation of a view controller

In code, to turn a view controller in a storyboard into a view controller instance, you start with a UIStoryboard instance, and call one of these methods:

instantiateInitialViewController

Loads the storyboard’s initial view controller nib and turns it into a view controller instance.

instantiateViewController(withIdentifier:)

Loads the nib of a view controller within the storyboard whose scene is named by an identifier string, and turns it into a view controller instance.

Loading a Main View Nib

A view controller has a main view. But for reasons of efficiency, a view controller, when it is instantiated, lacks its main view. It obtains its main view later, when that view is needed because it is to be placed into the interface. We say that a view controller loads its view lazily. A view controller can obtain its main view in several ways; one way is to load it from a nib. There are two main cases to consider:

View controller in a storyboard

When a view controller and its view belong to a scene in a storyboard, there are two nibs involved: the nib containing the view controller, and the nib containing the view. The nib containing the view controller was loaded in order to instantiate the view controller, as I just described; later, when the view controller instance needs its main view, it automatically loads that nib and generates its main view from it.

View controller instantiated in code

Another fairly common configuration is a view controller instantiated entirely in code, whose main view has been designed in a .xib file in your project. When you call the view controller’s designated initializer init(nibName:bundle:), the nibName: parameter tells this view controller instance the name of the nib file generated from that .xib file. Subsequently, when the view controller needs its main view, it automatically loads that nib and generates its main view from it.

Those two ways of getting the view controller’s main view are actually the same. In each case, there is a view controller and an associated nib. The view controller’s nibName property is the key; it tells the view controller what nib to load in order to generate its main view when it needs it.

Loading a View Nib Manually

A view can be designed in a .xib file. This file is turned into a nib. Assume that this is not a view controller’s main view. To load that nib and create the view instance requires a call to one of these methods:

loadNibNamed(_:owner:options:)

A Bundle instance method. Usually, you’ll direct it to Bundle.main.

instantiate(withOwner:options:)

A UINib instance method. The nib in question was specified when UINib was instantiated and initialized with init(nibName:bundle:).

Sometimes the runtime will make those calls for you. When you tell a table view (UITableView) to obtain its cells from a certain nib file, that nib file might be loaded many times by the runtime to generate the visible rows of the table.

Alternatively, you can make those calls yourself to load a nib view directly. That’s the best way to explore and exercise the nib-loading mechanism. Let’s try it!

First we’ll create and configure a .xib file in our Empty Window project:

  1. In the Empty Window project, choose File → New → File and specify iOS → User Interface → View. This will be a .xib file containing a UIView instance. Click Next.

  2. In the Save dialog, accept the default name, View, for the new .xib file. Click Create.

  3. We are now back in the Project navigator; our View.xib file has been created and selected, and we’re looking at its contents in the editor. Those contents consist of a single UIView.

  4. Our view is too large for purposes of this demonstration, so select it and, in the Attributes inspector, change the Size pop-up menu, under Simulated Metrics, to Freeform. Handles appear around the view in the canvas; drag them to make the view smaller. About 240×200 would be a good size.

  5. Populate the view with some arbitrary subviews by dragging them into it from the Library. You can also configure the view itself; for example, in the Attributes inspector, change its background color (Figure 7-6).

ios12 0706b
Figure 7-6. Designing a view in a .xib file

Our goal now is to load this nib file, manually, in code, when the app runs. There are three tasks you have to perform when you load a nib:

  1. Load the nib.

  2. Obtain the instances that it creates as it loads.

  3. Do something with those instances.

I’ve already said that to load the nib we can call loadNibNamed(_:owner:). This would be the complete code for loading our nib:

Bundle.main.loadNibNamed("View", owner: nil)

That’s the first task. But if that’s all we do, we will load the nib to no effect. The instances will be created and will then vanish in a puff of smoke. In order to prevent that, we need to capture those instances. Here’s one way to do that. The call to loadNibNamed(_:owner:) returns an array of instances created from the nib’s top-level nib objects through the loading of that nib. Our nib contains just one top-level nib object — the UIView — so it is sufficient to capture the first (and only) element of this array:

let arr = Bundle.main.loadNibNamed("View", owner: nil)!
let v = arr[0] as! UIView

We have now performed the second task: we’ve captured an instance that we created by loading the nib. The variable v now refers to a brand-new UIView instance.

Now let’s perform the third task — doing something with the view we’ve just instantiated. A useful and dramatic thing to do with it, and probably the reason you’d load a nib in the first place, is to put that view into your interface. Let’s do that! Edit ViewController.swift and put these lines of code into its viewDidLoad method:

let arr = Bundle.main.loadNibNamed("View", owner: nil)!
let v = arr[0] as! UIView
self.view.addSubview(v)

Build and run the app. There’s our view, visible in the running app’s interface! This proves that our loading of the nib worked (Figure 7-7).

ios13 0706c
Figure 7-7. A nib-loaded view appears in our interface

Connections

A connection is a directional linkage in the nib editor running from one object to another. I’ll call the two objects the source and the destination of the connection. There are two kinds of connection: outlet connections and action connections. The rest of this section describes them, explains how to create and configure them, and discusses the nature of the problems that they are intended to solve.

Outlets

When a nib loads and its instances come into existence, those instances are useless unless you can get a reference to them. In the preceding section, we solved that problem by capturing the array of instances returned from the loading of the nib. But there’s another way: use an outlet. This approach is more complicated — it requires some advance configuration, which can easily go wrong. But it is also more common, especially when nibs are loaded automatically.

An outlet is a connection that has a name, which is effectively just a string. When the nib loads, something unbelievably clever happens. The source object and the destination object are no longer just potential objects in a nib; they are now real, full-fledged instances. At that moment, the outlet’s name is used to locate an instance property with that same name in the outlet’s source object, and the destination object is assigned to that property. The source object now has a reference to the destination object!

Suppose that the following three things are true:

  1. As defined in code, a Dog has a master instance property which is typed as Person.

  2. There’s a Dog object and a Person object in a nib.

  3. We make an outlet from the Dog object to the Person object in the nib, and we name that outlet "master".

In that case, when the nib loads and the Dog instance and the Person instance are created, that Person instance will be assigned as the value of that Dog instance’s master property (Figure 7-8), just as if we had said dog.master = person in code.

ios8 0707
Figure 7-8. How an outlet provides a reference to a nib-instantiated object

As you can see, for an outlet to work, preparation must be performed in two different places: in the class of the source object, where the instance property is declared, and in the nib, where the outlet is created and configured. This is a bit tricky; Xcode does try to help you get it right, but it is still possible to mess it up. (I will discuss ways of messing it up, in detail, later in this chapter.)

The Nib Owner

Consider once again the view-loading example that we implemented earlier (illustrated in Figure 7-7). Let’s implement that example again; this time, instead of assigning the nib-loaded view to a variable in code, we’ll use an outlet connection to capture the nib-loaded view into a property.

Now, there is an important difference between the Dog-and-Person example I just outlined and the view-loading example. In our view-loading example, who is the Dog, and who is the Person? The Person is the view in the nib. But the Dog is the view controller (a ViewController instance) — and the view controller is not in the nib.

For our view controller to use an outlet to capture a reference to a view instance created from a nib, therefore, we need an outlet that runs from an object outside the nib (the view controller) to an object inside the nib (the view). That seems metaphysically impossible — but it isn’t. The nib editor cleverly permits such an outlet to be created, using the nib owner object.

Before I explain what the nib owner is, I’ll tell you where to find the nib owner object in the nib editor:

In a storyboard scene

In a storyboard scene, the nib owner is the view controller. It is the first object listed for that scene in the document outline, and the first object shown in the scene dock.

In a .xib file

In a .xib file, the nib owner is a proxy object. It is the first object shown in the document outline, listed under Placeholders as the File’s Owner.

So what is the nib owner object? It’s a proxy representing an instance that already exists outside the nib at the time that the nib is loaded. When the nib is loaded, the nib-loading mechanism doesn’t instantiate that object; the nib owner is already an instance. Instead, the nib-loading mechanism substitutes the real, already existing instance for the nib owner object, using the real instance to fulfill any connections that involve the nib owner.

But wait! How does the nib-loading mechanism know what real, already existing instance to substitute for the nib owner object in the nib? It knows because it is told, in one of two ways, at nib-loading time:

Your code loads the nib

If your code loads a nib manually, either by calling loadNibNamed(_:owner:options:) or by calling instantiate(withOwner:options:), you specify an owner object as the owner: argument.

A view controller loads the nib

If a view controller instance loads a nib automatically in order to obtain its main view, the view controller instance specifies itself as the owner object.

Let’s do a thought-experiment with our Dog and Person objects. This time, suppose the following four things are true:

  1. As defined in code, a Dog has a master instance property which is typed as Person.

  2. There is a Person nib object in our nib, but no Dog nib object.

  3. We configure an outlet in the nib from the nib owner object to the Person object, and we name that outlet "master". (We can’t do that unless the nib owner object’s class is Dog, so we’ll set its class first if necessary.)

  4. When we load the nib, we specify an existing Dog instance as owner.

The nib-loading mechanism will then match the Dog nib owner object with the already existing actual Dog instance that we specified as owner, and will assign the newly instantiated Person instance as that Dog instance’s master (Figure 7-9).

ios8 0708
Figure 7-9. An outlet from the nib owner object

Back in the real world, let’s reconfigure our Empty View nib-loading project to demonstrate this mechanism. In ViewController.swift, we’re already loading the View nib in code. This code is running inside a ViewController instance. We want to use that instance as the nib owner. This will be a little tedious to configure, but bear with me, because understanding how it works is crucial:

  1. We need an instance property in ViewController. At the start of the body of the ViewController class declaration, insert the property declaration, like this:

    class ViewController: UIViewController {
        @IBOutlet var coolview : UIView!

    The var declaration you already understand; we’re making an instance property called coolview. It is declared as an Optional because it won’t have a “real” value when the ViewController instance is created; it’s going to get that value later through the loading of the nib (“Deferred initialization of properties”). The @IBOutlet attribute is a hint to Xcode to allow us to create the outlet in the nib editor.

  2. Edit View.xib. We’d like to make the outlet, but in order to do that, we must ensure that the nib owner object is designated as a ViewController instance. Select the File’s Owner proxy object and switch to the Identity inspector. In the first text field, under Custom Class, set the Class value as ViewController. Tab out of the text field and save.

  3. Now we’re ready to make the outlet! In the document outline, hold Control and Control-drag from the File’s Owner object to the View; a little line follows the mouse as you drag. Release the mouse when the View is highlighted. A little HUD (heads-up display) appears, listing possible outlets we are allowed to create (Figure 7-10). There are two of them: coolview and view. Click coolview (not view!).

  4. Finally, we need to modify our nib-loading code. We no longer need to capture the result of our call to loadNibNamed(_:owner:). That’s the whole point of this exercise! Instead, we’re going to load the nib with ourself as owner. This will cause our coolview instance property to be set automatically:

    Bundle.main.loadNibNamed("View", owner: self)
    self.view.addSubview(self.coolview)
ios13 0708b
Figure 7-10. Creating an outlet

Build and run. It works! The first line loaded the nib, instantiated the view, and set our coolview instance property to that view. The second line can display self.coolview in the interface, because self.coolview now is that view.

Let’s sum up what we just did. Our preparatory configuration was a little tricky, because it was performed in two places — in code, and in the nib:

In code

There must be an instance property in the class whose instance will act as owner when the nib loads. It must be marked as @IBOutlet; otherwise, Xcode won’t permit us to create the outlet in the nib editor.

In the nib editor

The class of the nib owner object must be set to the class whose instance will act as owner when the nib loads; otherwise, Xcode still won’t permit us to create the outlet. We must then create the outlet, with the same name as the property, from the nib owner to some nib object.

If all those things are true, then, when the nib loads, if it is loaded with an owner of the correct class, that owner’s instance property will be set to the outlet destination.

Tip

When you configure an outlet to an object in the nib, that object’s name as listed in the document outline ceases to be generic (e.g. “View”) and takes on the name of the outlet (e.g. “coolview”). This name is still just a label — it has no effect on the operation of the outlet — and you can change it in the Identity inspector.

Automatically Configured Nibs

Now that we’ve created a nib owner outlet manually and loaded a nib manually, we have demystified the nib-loading mechanism. When a view controller gets its main view from a nib automatically, everything works exactly like what we just did.

Consider our Empty Window project’s Main.storyboard, with its single scene consisting of a ViewController and its main view:

  • In our manual example, we started with an instance property in our nib owner class. Well, ViewController is a UIViewController, and UIViewController has an instance property — its view property! This is the property that needs to be set in order for the view controller to obtain its main view.

  • In our manual example, in the nib editor, we made sure that the nib owner object’s class would be the class of the owner when the nib loads. Well, in our Main.storyboard scene, the View Controller object is the nib owner, and it is of the correct class, namely ViewController (the class declared in the ViewController.swift file). Look and see: select the ViewController object in the storyboard and examine its class in the Identity inspector.

  • In our manual example, in the nib editor, we created an outlet with the same name as the owner instance property, leading from the owner to the nib object. Well, in our Main.storyboard scene, the ViewController object is the view nib owner, and it has an outlet named view which is connected to the main view. Look and see: select the view controller object in the storyboard and examine its Connections inspector (Figure 7-11).

ios13 0708c
Figure 7-11. A view controller’s view outlet connection

So the storyboard has already been configured in a manner exactly parallel to how we configured View.xib in the preceding section. And the result is exactly the same! When the view controller needs its view, it loads the view nib with itself as owner, the nib-loading mechanism sees the connected view outlet, the view at the destination of that outlet is assigned to the view controller’s view property, and voilà! The view controller has its main view.

Moreover, the view controller’s main view is then placed into the interface. And that is why whatever we design in this view in the storyboard, such as putting into it a button whose title is “Hello,” actually appears in the interface when the app runs.

Misconfigured Outlets

Setting up an outlet to work correctly involves several things being true at the same time. You should expect that at some point in the future you will fail to get this right, and your outlet won’t work properly — and your app will probably crash. So be prepared! And don’t worry; this happens to everyone. The important thing is to recognize the symptoms so that you know what’s gone wrong. We’re deliberately going to make things go wrong, so that we can explore the main ways for an outlet to be incorrectly configured. The crashes I’m about to describe, especially the first two, are extremely common for beginners.

Outlet–property name mismatch

Start with our working Empty Window example. Run the project to prove that all is well. Now, in ViewController.swift, change the property name to badview:

@IBOutlet var badview : UIView!

In order to get the code to compile, you’ll also have to change the reference to this property in viewDidLoad:

self.view.addSubview(self.badview)

The code compiles just fine. But when you run it, the app crashes with this message in the console: “This class is not key value coding-compliant for the key coolview.”

That message is just a technical way of saying that the name of the outlet in the nib (which is still coolview) doesn’t match the name of any property of the nib’s owner when the nib loads — because we changed the name of that property to badview and wrecked the configuration. In effect, we had everything set up correctly, but then we went behind the nib editor’s back and removed the corresponding instance property from the outlet source’s class. When the nib loads, the runtime can’t match the outlet’s name with any property in the outlet’s source — the ViewController instance — and we crash.

There are other ways to bring about this same misconfiguration. You could change things so that the nib owner is an instance of the wrong class. You might do that in the nib editor, by selecting the nib owner and changing its class in the Identity inspector. Alternatively, you might do it in code:

Bundle.main.loadNibNamed("View", owner: NSObject())

We made the owner a plain vanilla NSObject instance. The NSObject class has no property with the same name as the outlet, so the app crashes when the nib loads, complaining about the owner not being “key value coding-compliant.”

Tip

To change an outlet property’s name without breaking the connection from the nib, select the property name in code and choose Editor → Refactor → Rename.

No outlet in the nib

Fix the problem from the previous example by changing both references to the property name from badview back to coolview in ViewController.swift. Run the project to prove that all is well. Now we’re going to mess things up at the other end! Edit View.xib. Select the File’s Owner and switch to the Connections inspector, and disconnect the coolview outlet by clicking the X at the left end of the second cartouche. Run the project. We crash with this error message in the console: “Fatal error: unexpectedly found nil while unwrapping an Optional value.”

We removed the outlet from the nib. So when the nib loaded, our ViewController instance property coolview, which is typed as an implicitly unwrapped Optional wrapping a UIView, was never set to anything. It kept its initial value, which is nil. We then tried to use the implicitly unwrapped Optional by putting it into the interface:

self.view.addSubview(self.coolview)

Swift tries to obey by unwrapping the Optional, but you can’t unwrap nil, so we crash.

No view outlet

I can’t demonstrate this problem using a .storyboard file. What we’d like to do is disconnect the view outlet in Main.storyboard, but the storyboard editor guards against this. But if you could make this mistake, then trying to run the project would result in a crash at launch time, with a console message complaining that “the view outlet was not set.”

A nib that is to serve as the source of a view controller’s main view must have a connected view outlet from the view controller (the nib owner object) to the view. In a .xib file whose view is to function as a view controller’s main view, you can make this mistake — usually by forgetting to connect the File’s Owner view outlet to the view in the first place.

Deleting an Outlet

Deleting an outlet coherently — that is, without causing one of the problems described in the previous section — involves working in several places at once, just as creating an outlet does. I recommend proceeding in this order:

  1. Disconnect the outlet in the nib.

  2. Remove the outlet declaration from the code.

  3. Attempt compilation and let the compiler catch any remaining issues for you.

Let’s suppose that you decide to delete the coolview outlet from the Empty Window project. You would follow the same three-step procedure that I just outlined:

  1. Disconnect the outlet in the nib. To do so, edit View.xib, select the source object (the File’s Owner proxy object), and disconnect the coolview outlet in the Connections inspector by clicking the X.

  2. Remove the outlet declaration from the code. To do so, edit ViewController.swift and delete or comment out the @IBOutlet declaration line.

  3. Now attempt to build the project; the compiler issues an error on the line referring to self.coolview in ViewController.swift, because there is now no such property. Delete or comment out that line, and build again to prove that all is well.

More Ways to Create Outlets

Earlier, we created an outlet by control-dragging from the source to the destination in the document outline. Xcode provides many other ways to create outlets — too many to list here. I’ll survey some of the most interesting. We’ll continue to use the Empty Window project and the View.xib file. All of this works exactly the same way for a .storyboard file.

To prepare, delete the outlet in View.xib as I described in the previous section (if you haven’t already done so). In ViewController.swift, create (or uncomment) the property declaration, and save:

@IBOutlet var coolview : UIView!

Now we’re ready to experiment:

Drag from source Connections inspector

You can drag from a circle in the Connections inspector in the nib editor to connect the outlet. In View.xib, select the File’s Owner and switch to the Connections inspector. The coolview outlet is listed here, but it isn’t connected: the circle at its right is open. Drag from the circle next to coolview to the UIView object in the nib. You can drag to the view in the canvas or in the document outline. You don’t need to hold Control as you drag from the circle, and there’s no HUD because you’re dragging from a specific outlet, so Xcode knows which one you mean.

Drag from destination Connections inspector

Now let’s make that same move the other way round. Delete the outlet in the nib. Select the View and look at the Connections inspector. We want an outlet that has this view as its destination: that’s a “referencing outlet.” Drag from the circle next to New Referencing Outlet to the File’s Owner object. The HUD appears: click coolview to make the outlet connection.

Drag from source HUD

Instead of starting by dragging, we can start by Control-clicking to summon a HUD and then drag from that HUD. Again delete the outlet in the Connections inspector. Control-click the File’s Owner. A HUD appears, looking a lot like the Connections inspector. Drag from the circle at the right of coolview to the UIView.

Drag from destination HUD

Again, let’s make that same move the other way round. Delete the outlet in the Connections inspector. Either in the canvas or in the document outline, Control-click the view. There’s the HUD showing its Connections inspector. Drag from the New Referencing Outlet circle to the File’s Owner. A second HUD appears, listing possible outlets; click coolview.

Again, delete the outlet. Now we’re going to create the outlet by dragging between the code and the nib editor. This will require that you work in two places at once: you’re going to need two editor panes (see Chapter 6). In one editor pane, show ViewController.swift. In the other editor pane, show View.xib, in such a way that the view is visible.

Drag from property declaration to nib

Next to the property declaration in the code, in the gutter, is an empty circle. Drag from that circle right across the barrier to the View in the nib editor (Figure 7-12). You’ve done it! The outlet connection has been formed in the nib; you can see this by looking at the Connections inspector, and also because, back in the code, the circle in the gutter is now filled in.

You can hover over the filled circle, or click it, to learn what the outlet in the nib is connected to. You can click the little menu that appears when you click in the filled circle to navigate to the destination object.

ios13 0712
Figure 7-12. Connecting an outlet by dragging from code to nib editor

Here’s one more way — the most amazing of all. Keep the two-pane arrangement from the preceding example. Again, delete the outlet (you will probably need to use the Connections inspector or HUD in the nib editor pane to do this). Also delete the @IBOutlet line from the code! We’re going to create the property declaration and connect the outlet, in a single move!

Drag from nib to code

Control-drag from the view in the nib editor across the pane barrier to just inside the body of the class ViewController declaration. A HUD offers to Insert Outlet or Outlet Collection (Figure 7-13). Release the mouse. A popover appears, where you can configure the declaration to be inserted into your code. Configure it as shown in Figure 7-14: you want an outlet, and this property should be named coolview. Click Connect. The property declaration is inserted into your code, and the outlet is connected in the nib.

ios13 0713
Figure 7-13. Creating an outlet by dragging from nib editor to code
ios13 0714
Figure 7-14. Configuring a property declaration
Warning

Making an outlet by connecting directly between code and the nib editor is cool and convenient, but don’t be fooled: there’s no such direct connection. There are two distinct and separate things — an instance property in a class, and an outlet in the nib with the same name and coming from an instance of that class. It is the identity of the names and classes that allows the two to be matched at runtime when the nib loads. Xcode tries to help you get everything set up correctly, but it is not in fact magically connecting the code to the nib.

Outlet Collections

An outlet collection is an array instance property (in code) matched (in a nib) by multiple connections to objects of the same type.

Suppose a class contains this property declaration:

@IBOutlet var coollabels: [UILabel]!

The outcome is that, in the nib editor, with an instance of this class selected, the Connections inspector lists coollabels — not under Outlets, but under Outlet Collections. This means that you can form multiple coollabels outlets, each one connected to a different UILabel object in the nib. When the nib loads, those UILabel instances become the elements of the array coollabels; the order of elements in the array is the order in which the outlets were formed. Your code can then refer to the labels by number (the index into the array). This can be cleaner than having a separate instance property for each label.

Action Connections

An action connection, like an outlet connection, is a way of giving one object in a nib a reference to another. But, unlike an outlet connection, it’s not a property reference; it’s a message-sending reference.

An action is a message emitted automatically by a Cocoa UIControl interface object (a control), sent to another object when the user does something to it, such as tapping the control. The various user behaviors that will cause a control to emit an action message are called events. To see a list of possible events, look at the UIControl.Event documentation. For example, in the case of a UIButton, the user tapping the button corresponds to the UIControl.Event.touchUpInside event.

For this architecture to work, the control object must know three things:

Control event

What control event to respond to.

Action

What message to send (that is, what method to call) when that control event occurs.

Target

What object to send that message to.

An action connection in a nib builds the knowledge of those three things into itself. It has the control object as its source; its destination is the target; and you tell the action connection, as you form it, what the control event and action message should be. To form the action connection, you need to configure the class of the destination object so that it has an instance method suitable as an action message.

To experiment with action connections, we’ll need a UIControl object in a nib, such as a button. You may already have such a button in the Empty Window project’s Main.storyboard file. However, it’s probable that, when the app runs, we’ve been covering the button with the view that we’re loading from View.xib. So first clear out the ViewController class declaration body in ViewController.swift, so that there is no outlet property and no manual nib-loading code; this should be all that’s left:

class ViewController: UIViewController {
}

Now let’s arrange to use the view controller in our Empty Window project as a target for an action message emitted by the button’s .touchUpInside event (meaning that the button was tapped). We’ll need a method in the view controller that will be called by the button when the button is tapped. To make this method dramatic and obvious, we’ll have the view controller put up an alert window. Insert this method into the ViewController declaration body:

@IBAction func buttonPressed(_ sender: Any) {
    let alert = UIAlertController(
        title: "Howdy!", message: "You tapped me!", preferredStyle: .alert)
    alert.addAction(
        UIAlertAction(title: "OK", style: .cancel))
    self.present(alert, animated: true)
}

The @IBAction attribute is like @IBOutlet: it’s a hint to Xcode itself, asking Xcode to make this method available in the nib editor. And indeed, if we look in the nib editor, we find that it is now available: edit Main.storyboard, select the View Controller object and switch to the Connections inspector, and you’ll find that buttonPressed:, which is the Objective-C name of our action method, is now listed under Received Actions.

In Main.storyboard, in the single scene that it contains, the top-level View Controller’s View should contain a button. (We created it earlier in this chapter: see Figure 7-4.) If it doesn’t, add one, and position it in the upper left corner of the view. Our goal now is to connect that button’s Touch Up Inside event, as an action, to the buttonPressed(_:) method in ViewController.

As with an outlet connection, there is a source and a destination. The source here is the button in the storyboard; the destination is the ViewController instance acting as owner of the nib containing the button. There are many ways to form this action connection, all of them completely parallel to the formation of an outlet connection. The difference is that we must configure both ends of the connection. At the button (source) end, we must specify that the control event we want to use is Touch Up Inside; fortunately, this is the default for a UIButton, so we might be able to skip this step. At the view controller (destination) end, we must specify that the action method to be called is our buttonPressed(_:) method.

Let’s form the action connection by Control-dragging from the button to the view controller in the nib editor:

  1. Control-drag from the button (in the canvas or in the document outline) to the View Controller listing in the document outline (or to the view controller icon in the scene dock above the view in the canvas).

  2. A HUD listing possible connections appears (Figure 7-15); it lists mostly segues, but it also lists Sent Events, and in particular it lists buttonPressed:.

  3. Click the buttonPressed: listing in the HUD.

ios10 0715
Figure 7-15. A HUD showing an action method

The action connection has now been formed. This means that when the app runs, any time the button gets a Touch Up Inside event — meaning that it was tapped — it will call the buttonPressed(_:) method in the target, which is the view controller instance. We know what that method should do: it should put up an alert. Try it! Build and run the app, and when the app appears in the Simulator, tap the button. It works!

More Ways to Create Actions

Other ways to form the action connection in the nib, having created the action method in ViewController.swift, include the following:

Drag from source Connections inspector

Select the button and use the Connections inspector. Drag from the Touch Up Inside circle to the view controller. A HUD appears, listing the known action methods in the view controller; click buttonPressed:.

Drag from source HUD

Control-click the button. A HUD appears, similar to the Connections inspector. Proceed as in the previous case.

Drag from destination HUD

Control-click the view controller. A HUD appears, similar to the Connections inspector. Drag from buttonPressed: (under Received Actions) to the button. Another HUD appears, listing possible control events. Click Touch Up Inside.

Drag from action method to nib

Make two editor panes. Arrange to see ViewController.swift in one pane and the storyboard in the other. The buttonPressed(_:) declaration in ViewController.swift has a circle to its left, in the gutter. Drag from that circle across the pane barrier to the button in the nib.

As with an outlet connection, the most impressive way to make an action connection is to drag from the nib editor to your code, inserting the action method and forming the action connection in the nib in a single move. To try this, first delete the buttonPressed(_:) method in your code and delete the action connection in the nib. Make two editor panes. Arrange to see ViewController.swift in one pane and the storyboard in the other. Now:

  1. Control-drag from the button in the nib editor to an empty area in the ViewController class declaration’s body. A HUD offering to create an outlet or an action appears in the code. Release the mouse.

  2. The popover view appears:

    1. Always look first at the Connection pop-up menu. It might be offering to create an outlet connection. That isn’t what you want; you want an action connection! If it says Outlet, change it to Action.

    2. Enter the name of the action method (here, buttonPressed) and configure the rest of the declaration. The defaults are probably good enough: see Figure 7-16.

ios11 0716
Figure 7-16. Configuring an action method declaration

Xcode forms the action connection in the nib, and inserts a stub method into your code:

@IBAction func buttonPressed(_ sender: Any) {
}

The method is just a stub (Xcode can’t read your mind and guess what you want the method to do), so in real life, at this point, you’d insert some functionality between those curly braces. As with an outlet connection, the filled circle next to the code in an action method tells you that Xcode believes that this connection is correctly configured, and you can click the filled circle to learn, and navigate to, the object at the source of the connection.

Misconfigured Actions

As with an outlet connection, configuring an action connection involves setting things up correctly at both ends (the nib and the code) so that they match. So of course you can wreck an action connection’s configuration and crash your app. So be prepared! The typical misconfiguration, commonly encountered by beginners, is that the name of the action method as embedded in the action connection in the nib no longer matches the name of the action method in the code.

To see this, change the name of the action method in the code from buttonPressed to something else, like buttonPushed. Now run the app and tap the button. Your app crashes, displaying in the console this dreaded error message: “Unrecognized selector sent to instance.” A selector is a message — the name of a method (Chapter 2). The runtime tried to send a message to an object, but that object turned out to have no corresponding method (because we renamed it). If you look a little earlier in the error message, it even tells you the name of this method:

-[Empty_Window.ViewController buttonPressed:]

The runtime is telling you (using Objective-C notation) that it tried to call the buttonPressed(_:) method in your Empty Window module’s ViewController class, but the ViewController class has no such method.

Tip

To change an action method’s name without breaking the connection from the nib, select the method name in code and choose Editor → Refactor → Rename.

Connections Between Nibs — Not!

You cannot draw an outlet connection or an action connection between an object in a nib and an object in a different nib:

  • You cannot open nib editors on two different .xib files and Control-drag a connection from one to the other.

  • In a .storyboard file, you cannot Control-drag a connection between an object in one scene and an object in another scene.

The reason is obvious when you consider what a nib is. Objects in a nib together will become instances together, at the moment when the nib loads, so it makes sense to connect them in that nib, because we know what instances we’ll be talking about when the nib loads. The two objects may both be instantiated by loading the nib, or one of them may be a proxy object (the nib owner), but they must both be represented in the same nib, so that the actual instances can be configured in relation to one another on each particular occasion when this nib loads.

If an outlet connection or an action connection were drawn from an object in one nib to an object in another nib, there would be no way to understand what actual future instances the connection is supposed to connect, because they are different nibs and will be loaded at different times (if ever). The problem of communicating between an instance generated from one nib and an instance generated from another nib is a special case of the more general problem of how to communicate between instances in a program, discussed in Chapter 13.

Additional Configuration of Nib-Based Instances

After a nib finishes loading, the instances that it describes have been initialized and configured with all the attributes dictated through the Attributes and Size inspectors, and their outlets have been used to set the values of the corresponding instance properties. Nevertheless, you might want to append your own code to the initialization process as an object is instantiated by loading a nib. This section describes some ways you can do that.

A common situation is that a view controller, functioning as the owner when a nib containing its main view loads (and therefore represented in the nib by the nib owner object), has an outlet to an interface object instantiated by the loading of the nib. In this architecture, the view controller can perform further configuration on that interface object, because it has a reference to it after the nib loads — the corresponding instance property. The earliest place where it can perform such configuration is its viewDidLoad method. At the time viewDidLoad is called, the view controller’s view has been instantiated and assigned to its view property, and all its outlets have been connected; but the view is not yet in the visible interface.

Another possibility is that you’d like the nib object to configure itself, over and above whatever configuration has been performed in the nib. Often, this will be because you’ve got a custom subclass of a built-in interface object class; in fact, you might want to create a subclass precisely so as to have a place to put this self-configuring code. The problem you’re trying to solve might be that the nib editor doesn’t let you perform the configuration you’re after, or that you have many objects that need to be configured similarly, so that it makes more sense for them to configure themselves by virtue of sharing a common class than to configure each one individually.

One approach is to implement awakeFromNib in your custom class. The awakeFromNib message is sent to all nib-instantiated objects just after they are instantiated by the loading of the nib: the object has been initialized and configured and its connections are operational.

Let’s make a button whose background color is always red, regardless of how it’s configured in the nib. (This is a nutty example, but it’s dramatically effective.) In the Empty Window project, we’ll create a button subclass, RedButton:

  1. In the Project navigator, choose File → New → File. Specify iOS → Source → Cocoa Touch Class. Click Next.

  2. Call the new class RedButton. Make it a subclass of UIButton. Click Next.

  3. Make sure you’re saving into the project folder, in the Empty Window group, and that the Empty Window app target is checked. Click Create. Xcode creates RedButton.swift.

  4. In RedButton.swift, inside the body of the RedButton class declaration, implement awakeFromNib:

    override func awakeFromNib() {
        super.awakeFromNib()
        self.backgroundColor = .red
    }

We now have a UIButton subclass that turns itself red when it’s instantiated from a nib. But we have no instance of this subclass in any nib. Let’s fix that. Edit the storyboard, select the button that’s already in the main view, and use the Identity inspector to change this button’s class to RedButton. Now build and run the project. Sure enough, the button is red!

A further possibility is to take advantage of the User Defined Runtime Attributes in the nib object’s Identity inspector. This can allow you to configure, in the nib editor, aspects of a nib object for which the nib editor itself provides no built-in interface. What you’re actually doing here is sending the nib object, at nib-loading time, a setValue(_:forKeyPath:) message; Cocoa key paths are discussed in Chapter 10. Naturally, the object needs to be prepared to respond to the given key path, or your app will crash when the nib loads.

One of the disadvantages of the nib editor is that it provides no way to configure layer attributes. Let’s say we’d like to use the nib editor to round the corners of our red button. In code, we would do that by setting the button’s layer.cornerRadius property. The nib editor gives no access to this property. Instead, we can select the button in the nib editor and use the User Defined Runtime Attributes in the Identity inspector. We set the Key Path to layer.cornerRadius, the Type to Number, and the Value to whatever value we want — let’s say 10 (Figure 7-17). Now build and run; sure enough, the button’s corners are now rounded.

Tip

If you define your own property, your User Defined Runtime Attributes setting for that property will fail silently unless you mark the property @objc.

ios13 0717
Figure 7-17. Rounding a button’s corners with a runtime attribute

You can also configure a custom property of a nib object by making that property inspectable. To do so, add the @IBInspectable attribute to the property’s declaration in your code. This causes the property to be listed in the nib object’s Attributes inspector. (It also implicitly marks it @objc.)

Let’s make it possible to configure our button’s border in the nib editor. At the start of the RedButton class declaration body, add this code:

@IBInspectable var borderWidth : CGFloat {
    get {
        return self.layer.borderWidth
    }
    set {
        self.layer.borderWidth = newValue
    }
}

That code declares a RedButton property, borderWidth, and makes it a façade in front of the layer’s borderWidth property. It also causes the nib editor to display that property in the Attributes inspector for any button that is an instance of the RedButton class (Figure 7-18). The result is that when we give this property a value in the nib editor, that value is sent to the setter for this property at nib-loading time, and the button border appears with that width.

ios13 0718
Figure 7-18. An inspectable property in the nib editor

To intervene with a nib object’s initialization even earlier, if the object is a UIView (or a UIView subclass), you can override init(coder:). A minimal implementation would look like this:

required init?(coder: NSCoder) {
    super.init(coder:coder)
    // your code here
}