That Weird Obstable of Interface Builder When You Get Started With iOS Development

A very strange obstacle to overcome when you just get started with iOS or macOS programming is to use custom views or view controllers. How does this magic even work? And what do the steps mean that you have to perform?

When you use a Xcode template, you get a Storyboard or a Xib with the most important components for an initial, empty app to display something on screen. This is accompanied by an AppDelegate and a custom ViewController class. If you don't know anything and just get started developing for iOS, say, then this is a good place to start hacking away: after all, the bare-bones infrastructure has been handled for you.

You will need to learn how to navigate around in an Xcode project, which takes time already, but apart from that, you can pretty much start copy & pasting stuff from the interwebs into a test project and see where this takes you.

But eventually you want to make a custom view. One that draws custom shapes, for example. If you have experience and know what you're doing, it's as simple as:

  1. Add a new file from a Cocoa class template to the project, CustomView, inheriting from NSView/UIView,
  2. override the draw(_:) method of the custom view with your drawing code,
  3. use CustomView as the type name in Xcode's Interface Builder to instantiate the view from the Xib/Storyboard.

That's "only" three steps, and they are instructional for me, and most iOS devs I can think of will know how to parse these instructions. But if you just get started, this is not helpful.

Then, you need a list like this:

  1. Create a new file for the custom view:
    1. Select "New" > "File…" in Xcode.
    2. In the template picker sheet that pops up, make sure your app's platform is selected at the very top (iOS or macOS).
    3. Then select the "Cocoa Class" template. Double-click or hit "Next" to continue.
    4. In the "Class:" text field, enter "CustomView". That'll be the name of the type you are about to create.
    5. From "Subclass of:", select "NSView" (or "UIView" on iOS). Leave the language to the default, probably "Swift".
    6. Select a place to save the file. The default that Xcode suggests is fine for now, but you may want to pay closer attention to this in the future when you start to organize files in custom groups. Proceed.
    7. Xcode's editor will open the file for you automatically.
      • You see a couple of lines of comment at the top that you can ignore.
      • The import statement makes sure that this file knows about the platform stuff provided by Apple.
      • The class CustomView: UIView { ... } (or class CustomView: NSView { ... }) block is the declaration of your class. Inside the curly braces, you can put method declarations that will be usable by instances of your view. We will implement the drawing routine there in a second.
  2. Write the custom drawing routine:
    1. Inside the curly braces that delimit all contents of your custom type, add a new line and then override the default drawing method by typing override draw(_ dirtyRect: NSRect) { }. If you type "draw", Xcode will show a couple of suggestions, where you can pick the correct method and have the declaration be generated for you. The dirtyRect parameter is passed into the view so it knows where it can draw. This is useful for large, scrolling views so you can redraw only the visible portion on screen, or a part below the mouse cursor. That's why it's called "dirty": it's the portion that needs redrawing.
    2. Inside the curly braces of this method, you can add your own drawing code. For now, we will fill the whole region of the view with a single color. To do that, add a line inside this draw method body that reads: UIColor.blue.setFill() to change the current fill color to blue. Add another line afterwards that reads: dirtyRect.fill(). That will use the currently set color and the rectangle portion that needs redrawing, which by default will be the whole area.
    3. The result should look like this: swift class CustomView: UIView { override draw(_ dirtyRect: NSRect) { UIColor.blue.setFill() dirtyRect.fill() } }
  3. Hook your new view type to be used by Interface Builder:
    1. Open your MainMenu.xib/Main.storyboard/… in Xcode.
    2. In the leftmost sidebar of the editor portion of Xcode, select your ViewController scene. That should make it visible if it wasn't already, which can sometimes happen if you open a file in Interface Builder after a long while. [Here, I'd add screenshots to highlight which sidebar I'm talking about, because Xcode's default view has a sidebar to the left with the project navigator, a sidebar to the right with file info, and then the Interface Builder view has its own outline structure.]
    3. Expand your ViewController scene by ⌥-clicking the rightward-facing triangles. That should expand (and collapse) the whole sub-tree.
    4. From the expanded view hierarchy that this outline represents, select the innermost View. That's the default content view of your ViewController, which is a feature-less UIView by default. We are going to change it to our custom view type.
    5. With the View selected, make sure the right inspector sidebar in Xcode is visible where you can inspect properties of whatever is currently selected inside Xcode. You can use the rightmost toolbar buttons of Xcode to toggle the sidebar. The inspector sidebar has a couple of icons at the top to change its currently visible tab. We want the "Identity Inspector", which in current Xcode versions is the 3rd tab. (Hover over the icons to see the tab names.) You can also quickly jump to this inspector tab by hitting ⌘⌥3, which will also show the sidebar if it wasn't visible before. I recommend you memorize this because you'll need it a lot.
    6. In the "Identity Inspector" sidebar, with the View still selected, you will see at the very top that the "Custom Class" group of properties has a drowdown labeled "Class". It's set to a placeholder value of "UIView" by default. In that field, type "CustomView" to select your own view. This instructs Interface Builder to create an instance of your new type instead of an empty blank view when the app launches.

See how much explaining I did? I even erred on the side of brevity here, omitting a few explanations that I found to be instrumental to understanding what's going on when I teach people Swift and using Xcode.

The last step, wiring things up in Interface Builder, is just the weirdest. And it's very fragile: when Xcode changes, all screenshots will be outdated. This happened a couple of times in the past decade. It's hard to explain what's going on in words, too. Opposed to code, you cannot show the result in a single snapshot. You have to describe the process, and to describe the process, you have to describe the steps involved. Nobody enjoys reading or writing these kind of instructions. And you cannot give a reader instructions like you'd give a machine, e.g. in UI tests, where you'd say app.buttons["Identity Inspector"].tap() if you're lucky, or moveMouse(1234, 300) to define absolute coordinates (which doesn't work, of course, so you have to stick to "toolbar button in the top-right of the window").

If you, the reader, do not have access to higher-level concepts like "create a file from template", or "change the type of the view in IB", then all of this gets very, very weird.

What does it mean to enter the "CustomView" type name in that particular sidebar that is only visible when you have an Interface Builder document open and select a view (or other object) inside it?

To help one of my pupils understand this, I eventually resorted to story mode:

The document that you edit in Interface Builder is essentially saving an instruction for your app to create your user interface for you. You could write it in code, but this is a bit simpler, and you have visual feedback in the editor, which is nice, especially when you just get started. When we have a look at the documentation of UIView, we can see that there's an initializer called init(coder:) that you probably will not call for quite a while in your code: it is used by the part of your app that understands Interface Builder files to create a new object instance, though. During app launch, a "coder" is used to decode the declarations from the Interface Builder file and make an object of your custom type for you. If we rely on these instructions, we have to tell the decoder that your CustomView does exist, and that you want an instance of that instead of the default UIView here. The "Identity Inspector" is concerned with all things that pertain, well, the identity of the view, its type and some other fundamental settings. It's visual properties, like size, or textual content in case of text labels, is grouped under different inspectors. So to tell the decoder which view object type you want, you have to change the "identity" instructions so that the "Class" that is used is known by the name "CustomView". To do that, we have to expand the inspector sidebar first, then head to the third, the identity inspector, by clicking …

The story about decoding and what happens here apparently resolved some confusion. It's good I do know this stuff and can produce a very shallow description of the goings-on here to help others learn and understand the steps involved. Because rote repetition will only get you so far. I still forget to change the class name in my Interface Builder files every other day, and I program for Apple platforms on a daily basis for 8 years now. It's second-nature to double-check this place when things don't look as expected. But the actual setup is still arcane when I try to take on the glasses of someone new to the platform.

Like, have you ever tried to do a remote screen sharing session and tell the other person which "tab" you mean when you look at the Xcode project settings? You have the Xcode tab bar with currently open files. You have tab icons in the navigator sidebar to the left; you have tab icons in the inspector sidebar to the right; you have tabs in the debug console pull-up-thingamob at the bottom. And you have a white editor screen with another level of master–detail-views with a sidebar of project targets and a tab-bar at the top with "Build Settings" and "Info" and the like that do not even look like interactive elements!

Close your eyes, forget that you know Xcode well, then try to make sense of what you see in this picture. Describe the portions on screen to a layperson. I’m glad I know that the left and right sidebar have distinct names (“navigator” and “inspector”, respectively) so I can refer to them by name as well to not rely on spatial arrangement forever in conversations.

It is just so much stuff. So – much – stuff!

I feel at home in Xcode, I really do. When I open the app and whip up a project, it's like an instant stress-relief. That's my place, alright.

But I have a really hard time helping other people on board.

And I do remember, faintly, how awkward it felt to get around in Xcode back in 2012. Back then, Interface Builder was a separate tool with a separate window. Nowadays, the main Xcode window does never change and simple swaps out the editor compartment and the sidebar contents according to context. This is the diametral opposite of a modeless interface. You have to know which mode you're in to get where you want to go. You cannot change the editor from an Interface Builder pane to a code file and have the "Identity Inspector" stay open, like if you want to look up the name of your type again, which you forgot because all this is so much overhead, so much you have to keep in mind, that you just have to get back to the code file to make sure you copy the type name to clipboard, but oh no, the inspector has changed to totally useless file information or a live documentation snippet that you may have never seen before, and which makes you afraid, so what now, yes, get back to the Storyboard file, but now everthing is hidden again and you have to figure out where the view was, what you had to click to get to the scene, wondering why the Storyboard preview is empty at all in the first place (is this a bug? is my app broken? oh god did I make a backup?), and then finally you get there, and you do not even understand what's going on.

So yeah. I think this is a very weird, and through its weirdness a very steep learning curve for beginners. The tools are powerful, and they look much better than they did in 2012, but I do like to think that the dedicated Interface Builder window helped me to make sense of the different parts of app development one at a time.

People still seem to get used to this, so it's not that bad, right? Well, yeah, it's just a pain to explain. I hope we all figure out good ways to teach newcomers this stuff so we all get the bonus of proficient developer colleagues – instead of mortally afraid computer users who try not to step into hidden landmines inside the foreign environment that they have to use to make an app work. This Interface Builder stuff is just the weirdest overhead, in my opinion, but it's also very useful to get started with IB instead of doing all things in code, because you get results quickly and get a feeling for the UI components early. At the cost of having to grok a totally different tool on top of the new language and framework.

Browse the blog archive