Working with User Interface files in Oxygene “Nougat”
In this article, i want to talk a bit about working with user interfaces in Oxygene Nougat.
As you know, Nougat is a native compiler for the Objective-C runtime, meaning that it works directly with the classes provided by Apple’s Cocoa and Cocoa Touch frameworks. This extends from low-level classes such as NSString or NSArray to the high-level visual components based around NSView (Mac) and UIView (iOS).
One common way for Mac and (especially) iOS apps to work with UI is to simply create the necessary views and controls that make up an app’s UI from code. But sooner or later, especially when dealing with more complex or sophisticated user interfaces, you will want to use the visual designer. This works on the same principles, whether you are using Xcode/Objective-C or Nougat.
Mac and iOS interfaces are designed in Interface Builder, which as of version 4 is directly integrated into the Xcode IDE, and when working with Nougat, that is where you will work with your interfaces, getting the same experience and the same power and flexibility of UI design that developers using Objective-C get, as well.
There are two file formats used for designing UI on Apple’s platform — the older XIB format and the newer (and iOS-only) storyboard format. The principles for dealing with these files are similar, but for the topic of this article, we’ll focus on XIB files, as those apply to both Mac and iOS development. In a future article, i will go into some more details specific to storyboards.
Background: What are XIB Files?
So what are XIB files? From the point of view of the UI designer, XIB files contain the views or windows that you design in Interface Builder — the controls, their layout and properties, and their connections to your code.
It is important to understand that on a technical level, XIB files are stored object graphs. That means that an XIB file, essentially, is a hierarchical set of objects descriptions. When an XIB file gets loaded at runtime, all the objects defined in the XIB file get instantiated, configured, and connected as described in the XIB.
These objects can be a combination of standard framework classes (such as NSView/UIView, NSButton/UIButton, etc), classes from third party libraries, or even classes defined in your own application code. When the Cocoa runtime loads an XIB, it goes thru the list one by one, looks for the classes with the appropriate names and news up the necessary objects.
Each XIB file also knows about a special object called the “File’s Owner”. This object will not be created when the XIB is loaded. Rather, the object that initiated the loading of the XIB file will take the place of the File’s Owner within the XIB’s object graph — including any connections and references to it. We will see how that is useful and important, soon.
Loading up an XIB
When and how do XIB files (or storyboards) get loaded? There are several possibilities:
If your Info.plist contains an NSMainNibFile entry, the Cocoa runtime will automatically load up that XIB as your application starts up. The global NSApplication/UIApplication instance will become File’s Owner of the XIB, and every object in your XIB will be instantiated.
This mode is common for Mac applications, and in fact you can see it in action in our Cocoa Application template(s). You probably noticed that (aside from the startup code in Program.pas) the project contains an AppDelegate class that is usually used as the “launching point” for your application’s own code.
How does this AppDelegate class get instantiated? Easy: If you own the MainMenu.xib file in Xcode (the xib that is specified to be the NSMainNibFile in Info.plist), you see that — among other elements — it contains an AppDelegate item. This is your own AppDelegate class.
For simple applications, you can get away with just putting all your stuff into MainMenu.xib, but as applications get more complex, that’s a bad idea, not only because — as indicated above — when an XIB is loaded, all objects referenced in it are created. If your application contains dozens of windows or views, you don’t usually want all of those to be loaded up as your application starts.
Instead, it is common practice to pair individual XIBs for each view or window XIB with a matching ViewController or WindowController class — a practice that you will see in just about all the iOS project templates, and also in the *Controller item templates we provide with Nougat.
How does this work?
Simple: your application will define a custom class, usually descended from UIViewController (or NSViewController/NSWindowController) where you will put all the application logic for that view or window. As far as your app is concerned, this class is your implementation for that particular view (or window — for simplicity reasons i’ll stick to talking about iOS Views for now, but the same concepts apply for OS X views and windows).
In the initializer for your view controller, you will ask the base class to load up the XIB file that backs your view, for example by calling
self := inherited initWithNib('MyViewController') bundle(nil);
This essentially tells the base class to initialize it by loading MyViewController.xib (from the main application bundle) and creating all the objects defined in it.
So all those objects get loaded up, but how do you then get access to them from your code? Simple: remember when i said above that the object loading the XIB becomes the File’s Owner? When you load an XIB using the initWithNib() call, your view controller class becomes the File’s Owner — and any connections you set up in the XIB between the File’s Owner and the other elements in your XIB will be connected to your view controller class.
Did i say connections? So how does this work?
Easy, really. XIB files know about two basic kinds of connections between objects: Outlets and Actions.
You can think of outlets as references to other objects. If your view controller class has a property of type UIButton, and your XIB file contains a UIButton, that’s a match made in heaven. You can just Ctrl-drag the button onto the File’s Owner (or vice versa) in the XIB to hook them up, and now you have access to the UIButton from your code, because as the XIB gets loaded and the UIButton gets created, it gets hooked up to your property automatically.
Actions, you may have guessed, can be thought of as events. If something happens with the objects in the XIB (such as a button being tapped), they send out events. Just as above, if your view controller exposes a method with the right signature (that is, any method with exactly one parameter of type “id” or a concrete class), you can Ctrl-drag it into your XIB file to hook them up — and when the event triggers, that method is called.
Of course outlets and actions can be hooked up between any two objects inside your XIB, not just with the view controller. For example, you can cause an action on one control to trigger a method on a different control.
Ok, so how does the XIB designer in Xcode know about the methods and properties on your view controller (or other classes)? Magic! As you write your classes, Nougat will automatically* update the XIB and Storyboard files, behind the scenes, with information about all the relevant classes and their properties and methods — that is any property marked “[IBOutlet]” and any method marked “[IBAction]“. As you work on your XIB file in Xcode, it sees this information, and makes the connections available.
If you need to expose a new control to your code or want to hook up a new event, simply add a new property or method to your code, and that’s it.
Let Me See This in Action
For this example, i’ve created a new “UIViewController with XIB” from the template. I have then added the following items to the “MyViewController” class:
property myButton: UIButton; property myLabel: UILabel; method buttonTapped(aSender: id);
The following screenshot explores the XIB (and storybook) designer in Xcode:
Figure 1: On the left side of the window, you see a hierarchical view of all the objects in the XIB — this includes all visual objects (in this case just the one UIView for now, but also other objects such as the File’s Owner).
On the right side, the “Utilities View” has the “Identity Inspector” pane activated, showing details about the selected object (the File’s Owner). Note that the XIB designer knows that File’s Owner is a “MyViewController”. It got that information from the template – but this is editable, so you can just type in or select the right class name. Of course it should match the class that is loading this XIB at runtime.
Figure 2: We have dropped a couple of controls onto the view — you can see them both visually and in the hierarchy on the left. The right pane has been switched over to the “Connections Inspector” tab, which shows all the connections available on our File’s Owner. Among them, you see our two properties and the method. There’s also a “view” property (defined by the UIViewController base class), already connected to the root view.
Figure 3: Click and drag from the little circle right of the “myButton” name to the button to make a connection to the UIButton. (You can drag to the control on the design surface or to the “Button — Tap Me!” item in the hierarchy.)
Let go when you are over the button, and the connection is made. If you were to go and build your app now, the myButton property would give you access to the button from code.
Figure 4: You can also drag from the hierarchy view to a control. When you let go, the XIB designer will present a list of all properties that match — in this case the UILabel qualifies both for “myLabel”, and for the “view” property (because UILabel descends from UIView).
Figure 5: Connection actions work the same way. You can Ctrl-drag from the control to the receiver (here the File’s Owner) to connect the default action of that control (in this case, the button tap) to a method. As you can see, the Connections Inspector also shows a complete list of all actions that can originate from the control, so you can — if needed — assign them all to individual methods.
Now all that’s left to do is maybe write an implementation for “buttonTapped” such as this:
method MyViewController.buttonTapped(aSender: id); begin myLabel.text := myButton.titleLabel.text; end;
to see both actions and outlet access in — pun not intended — action.
Terminology: XIB vs. NIB?
In the text above, i talk about XIB files, but the method names all mention NIBs. What’s up with that?
XIBs are a newer, XML based format that is used for the UI at design time. When you compile your app, the XIB files are converted to binary NIB files for you, and those binary versions of the files are embedded into your app. All the APIs working with these files predate the new format (and, at runtime, only work with the older NIB format), that’s why all the method names refer to NIB, not XIB. When you pass around names, you never need to (or should) specify the file extension anyway, so this is a distinction that you can largely ignore (unless you want to go spelunking into your .app bundle).
What’s “First Responder”?
Similar to File’s Owner, “First Responder” is another placeholder object exposed in the XIB file that has special meaning. The First Responder is not a concrete object, but essentially refers to “the first object that has focus that can handle this”.
By connecting actions to the First Responder, you can have them dynamically be sent to different parts of your application, depending on the state your app is in. A good example is the “Edit|Copy” menu in a Mac application. If a text field has focus, you would expect the Copy command to apply to the content of that text field. If a different control has focus, different content would be copied. By connecting the menu’s action to the First Responder’s “copy:” method, Cocoa will take care of calling “copy()” on whatever control or view has focus — in fact, all you need to do to make Copy/Paste work with your own custom view would be to implement the corresponding methods, and they would get called if your view has focus as the user invoked the menu item (or Cmd-C keyboard shortcut).
I hope this gave you a quick introduction to XIB files and how they work. A good 95% of the content of this article is not really specific to Nougat; the same concepts and techniques apply to working on XIB files with Objective-C — that’s by design, because Nougat is a true first class citizen on the Cocoa frameworks and Objective-C runtime.
(* in the current beta drop, this update does not happen automatically yet. You can right-click your .XIB or .storyboard file in Solution Explorer and choose the “Sync XIB”/”Sync Storyboard” to trigger an update.)