How is an .xib file translated to a UI element? - cocoa

From what I understand, MainMenu.xib contains XML that is used to create the main menu for an application. My question is, how is this file loaded, read, and used to create the main menu? Can anyone help me to understand the inner workings of the code that translates MainMenu.xib into the actual main menu seen in the UI?
Here is my understanding of the process so far.
MainMenu.xib is compiled into an .nib
The .nib is loaded into a UINib object.
From here, the UINib object is somehow used to create the Main Menu.
This is the part I am confused about. How exactly is the UINib object used to create the Main Menu?

Imagine this the other way around: you have a live view hierarchy in memory, in your running application. Now you want to allow the program to be terminated while saving the state of the UI. How would you do that? You would somehow write all the important properties of the live views to disk; some kind of serialization and archiving process.
That's all a nib is: it's an archive of a bunch of objects that happen to be views. (It encodes the "base" state of the views, of course, before any user interaction.) A xib, as you correctly noted, is an XML rendering of that archive, which makes it cooperate better with version control.
If you take a look at the XML of a xib in Xcode, you'll see that it consists of entries like this:
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="161" id="KGk-i7-Jjw" customClass="LeakInfoStepsCell" customModule="LookoutEmailLeakAlert" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="170"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
...
Just a listing of various properties on the view and the values they should have when the nib is unarchived. So when you load a UINib, it's reading those properties and creating view objects, just as you might do manually in code, or as you would do for your own object via NSCoding.

Related

When an element is dragged from Storyboard to ViewController, where is it linked programmatically?

As I understand it, each element such as a button or label is linked from Storyboard to ViewController. This is done graphically in XCode by dragging. Where is the code that states this pairing in the XCode environment?
There is no code that states this. In fact, there is no actual connection being made at all. It's just an illusion.
There are two separate things being configured when you do that:
In the nib (the storyboard), an outlet is configured. It is just a name, a string
In the code, there is a property with the same name.
The connection between them happens, if it is going to happen at all, when the app actually runs and at some point the nib is loaded. At that moment, the nib has an owner, and key-value coding is used to look for a property of that owner with the same name as the outlet. If it's there, the object instantiated from the nib is assigned as the value of that property, and thus the connection is made.
So you see, there is no real connection in Xcode. There are two separate things that will need to try to connect while the app actually runs, at nib-loading time. It's a fragile and risky business. That is why, if there is no such property, your app crashes at that moment.
Basically #IBOutlet is not part of Swift, and that's what lets Xcode figure out that outlet property inside a controller is linked to some UIView (or subclass, such as UILabel) on the Storyboard.
Also, if you right-click the Storyboard, and Open As > Source Code, you should see a lot of the generated UI/XML code. If your label is called myLabel, you should see something like this:
<connections>
<outlet property="myLabel" destination="zLp-2i-ru4" id="Zda-UD-1h6"/>
</connections>
inside one of the Scene Nodes.

What is the File's Owner (in Interface builder)?

I am new to Cocoa and I don't understand the concept of File's Owner of a .nib file.
Here is the way I would see things :
Consider a file myNibFile.nib file that describes how a window looks.
Now, I want to connect an actual window to this .nib file. So, I create a class myWindow, which is a subclass of NSWindowController. And, to do this connection, I change the init method like this:
-(id)init
{
[super initWithWindowNibName:#"myNibFile"];
return self;
}
So, I understand that when I create an instance of myWindow, the "system" will go and look at the .nib file and create the adequate object.
So, my question are :
why do I have to specify that the File's Owner of my .nib file is myWindow ? Isn't it redundant ?
I guess it means I didn't really understood what the File's Owner. What is it ? Why does the .nib file have to belong to something ? Can't it be "somewhere" in my "application" and when it is needed, the "system" goes there and use it ?
Thanks for helping me to see more clearly in these new concepts !
Two points to be remembered:
The File owner is the object that loads the nib, i.e. that object which receives the message loadNibNamed: or initWithNibName:.
If you want to access any objects in the nib after loading it, you can set an outlet in the file owner.
So you created a fancy view with lots of buttons, subviews etc . If you want to modify any of these views / objects any time after loading the nib FROM the loading object (usually a view or window controller) you set outlets for these objects to the file owner. It's that simple.
This is the reason why by default all View Controllers or Window Controllers act as file owners, and also have an outlet to the main window or view object in the nib file: because duh, if you're controlling something you'll definitely need to have an outlet to it so that you can send messages to it.
The reason it's called file owner and given a special place, is because unlike the other objects in the nib, the file owner is external to the nib and is not part of it. In fact, it only becomes available when the nib is loaded. So the file owner is a stand-in or proxy for the actual object which will later load the nib.
Hope you've understood. I'll clarify any of the points if you ask.
The fundamental thing to understand is that Interface Builder allows you to create objects that are automatically connected to each other, with no effort on the part of your program. You can instantiate all kinds of objects including non-view ones, and they can be inter-related; for example, you might create the instance of a table view data source along with the view itself, etc. This mechanism is commonly used to create an Application Delegate within your Main Menu NIB.
However, since it's all done via drag&drop, it seems there is no way that you can form a connection between any of the NIB objects and the objects that already exist in your application, with one exception.
When code is loading the NIB file, you have the option to specify exactly one object which the NIB will consider to be the "Files Owner". This is the placeholder that you see inside Interface Builder; since it can represent any object within your application, Interface Builder can't know what actions/outlets are available on it. This is why you can modify the "class" of the Files Owner, in the Attributes tab.
Files Owner does not really represent "ownership" or "parenthood". What it represents is "the object that loaded this NIB".
File's Owner is a placeholder in IB so all the outlets and actions in your code are "linkable" in IB, you can control-drag to connect stuff on the screen to the code.
File's Owner in Interface Builder is so that it knows the object type of the parent. This is used in two ways. Firstly, in Interface Builder so that IB knows what outlets and actions are available to you to connect. Secondly it is used by the application framework to know how to reconnect things to the rest of your code once the nib file is loaded.

nsdocument nswindowcontroller nsviewcontroller - one xib?

Is this a good practice, to have one xib (Document xib), and many nswindowcontrollers and nsviewcontrollers.
This is better because, you don't have to bind properties between multiple xibs
But what about cons?
Cons are:
You have to load the entire nib, so even if you only need one of the windows, you have to load them all.
Complex nibs can get unwieldy.
The "File Owner" is your document instead of your window controller, which encourages you to circumvent the window controllers, binding and connecting the view directly to the model.
When you have one nib per window, the file owner is usually the window controller, which brokers access to the document or exposes it as a property. Sometimes you do want to bind e.g. array controllers and object controllers to the document, but accessing it through the window controller gives you an opportunity to monitor the dependencies.

Sharing an NSArrayController between multiple views in separate NIB files

First, some background: I am trying to implement a master-detail interface in Cocoa (for OS X). That is, I have a window with two NSTableViews that display two different types of objects. For this question, let's say they are warehouses and packages (to pick an example that is analogous to my actual problem.) Selecting a row in the first table view (on a warehouse) will display a list of packages which belong to that warehouse in the second table view. For the model part, I currently have an NSMutableArray called warehouses of warehouse objects, and each warehouse object has an NSArray of package objects. One thing to note is that the warehouses variable is modified after the NIB files are loaded, so the NSArrayController has to be notified.
Now, I've tried to organize it so that the "master" is in its own view object, and the "detail" is in its own view object. This means that there are three NIBs: a WarehousesView NIB, WarehouseDetailView NIB, and a MainWindow NIB.
The WarehousesView NIB contains an instance of a WarehousesViewController (subclassed from NSViewController) and the view itself.
The WarehouseDetailView NIB contains an instance of WarehouseDetailViewController and the view itself.
The MainWindow NIB contains the main window, an instance of MainWindowController, and an instance of both WarehousesView and WarehouseDetailView. The window itself contains an NSSplitView, and the views of the split view are connected to the corresponding view instances in the NIB file.
That brings me to the first half of my question:
1) Is this a good way of splitting up the application views for a Cocoa application? To me it makes sense, because at a later point more details about the warehouse besides a list of its package inventory might be added to the WarehouseDetailView.
It's an important question, because everything works just fine if I skip creating views, putting all controls in the window directly and put everything else, including NSArrayController instances corresponding to Warehouses and Packages, into the same NIB file. I don't need to ask the second half of the question if I shouldn't even be doing it this way.
The second half of the question is basically:
2) Where should I place the NSArrayControllers corresponding to Warehouses and Packages if I split it up as described above so that the master-detail interface still works? Currently I am using Cocoa bindings, so somehow the content array of the Warehouse NSArrayController needs to bind to my warehouses array, and the content array of the Packages NSArrayController needs to bind to the selection of the Warehouse NSArrayController
I've tried a few things, and I couldn't get anything to work completely. Specifically, I've tried putting the NSArrayController for Warehouses into the WarehousesView NIB and the NSArrayController for Packages into the WarehouseDetailView NIB. The problem with this approach is that I cannot figure out a way to bind the Package NSArrayController to the selection of the Warehouse NSArrayController. The other thing I've tried is (1) putting both NSArrayControllers into the MainWindow NIB, (2) connecting those NSArrayControllers to IBOutlets in the MainWindowController, then (3) passing those variables to their respective view controllers via their constructors, (4) exposing them as properties in the view controllers via KVC, and (5) binding the necessary table columns in a view to the array controller through the File's Owner. The result was that nothing appeared, but there were no errors either. If one of these approaches is the preferred way to do it, I can give more details to help see if I am doing it incorrectly.
Thanks in advance!
Edit: I did look at this related question, and they seemed to be using separate instances of NSArrayControllers for each NIB file if I understood it correctly, and that didn't seem to make sense from a design point of view, but perhaps I am wrong?
Part 1: You can certainly do this. I'd say it's a matter of preference. Personally, then, if the views were going to be displayed simultaneously in a window, I would keep them in the same nib.* Modularity is also a good thing, though.
Part 2: You can put the array controllers wherever you like, really. The only thing you need to worry about is getting each object the references that it needs to the information you want it to have. If you want my 2¢, I'd say put each in the nib with the view its contents will be displayed in. That'll make your detail view setup more difficult, but it continues the modularity you seem to be going for.
You have to remember that every object in the nib is a real instance. The nib allocates and inits them for you; if you put a MyClass object in one nib, and a MyClass object in another nib, those are two different objects. This is sometimes a tricky thing about nibs: it's really convenient to have instances automatically created for you, but it also means some fiddling around with references when you want to do things across nibs.
It sounds like you put instances of WarehouseView and WarehouseDetailView into both your individual nibs and MainMenu.nib and expected them to be the same objects. It won't be so. You have to link objects in the nibs to objects that they already know about. You'll have to work this out for your particular situation.
I don't know where your model is stored, or how you're getting the nib loaded. Whichever object it doing that loading, though, will likely be your link between the individual nib and the rest of the app. This is what the File's Owner proxy object in nibs is for -- it gives you a place to hook up objects in the nib to code that they wouldn't otherwise know about.
*: If you find it easier to layout the views if they are not enclosed in the split view in IB, you could set them up on their own: put the custom view objects in the MainMenu.xib window and you can open each view in its own IB window (though it will not be in a window in the app). Then set the split view's subviews in something's awakeFromNib.

NSView high-level overview?

I am trying to create a custom view and load it into a window.
In the end, I know there will be a few steps to follow, reflecting the key relationships that I am missing. I am hoping someone can explain, in a series of steps, how one creates a view object, view controller, associated .xib, and then loads it into a window (after clearing the current view).
And I mean the real nitty gritty of where to declare and initialize, what needs to be imported, everything. Because I am looking through every book I have and it is just not clear to my puny brain.
Thanks!
how one creates a view object, view controller, associated .xib, and then loads it into a window …
These are several things, and some of them conflict.
If you create a view in code, you don't need to (and shouldn't) also create it in a nib, and vice versa.
If you create a view controller to load the nib, you will be creating the view in a nib, so you definitely should not create the same view in code.
You do not need to create a view controller for most views. It is more common to have each controller own the entirety of exactly one window. The only time you need view controllers is when you manage a complex view hierarchy in a single window (most likely if you make your application single-window).
… (after clearing the current view).
There is no “current view” in Cocoa. You can have multiple windows, and each has a deep view hierarchy that you usually don't edit at run time. Swapping one view for another outside of some sort of tabbed UI is very unusual.
Creating a view object in code
Send the desired view class an alloc message and the returned view an initWithFrame: message (unless otherwise prescribed by the class's documentation). You will, of course, need to release or autorelease this view.
Creating a view object in a nib
Giving it its own nib (especially for view controllers)
Use the view-nib template in IB (New) or Xcode (Add File). If you create it in Xcode, don't forget to get info on it and make it localizable. If you create it in IB, you should save it into one of your .lproj folders; then it will already be localizable.
A nib created from those templates will contain one empty NSView. You can change its class and/or add subviews as described below.
Making it in an existing nib
Drag “Custom View” from the Library palette into the nib window, then set the view's class on the ⌘6 inspector.
You only do this for the top-level view in the nib. For its subviews, see below.
Putting the view into a window's view hierarchy
If the view should be the root of the window's view hierarchy (the window's content view)
Set the window's content view.
In IB, you can't change the window's content view. Instead, you change things about it—its class, subviews, etc. There is no reason to try to replace the window's content view with another view in IB.
If the view should be a subview of an existing view
The way to do this in code is to send the superview an addSubview: message.
If both views are in the same nib, create the subview and add it to the superview in the same act. Drag “Custom View” from the Library into the superview, not the nib window, then set the subview's class on the ⌘6 inspector.
(If you're customizing one of the standard Apple views, rather than making a completely original custom view, drag the standard Apple view you based yours on from the Library, then change its class to your customized subclass.)

Resources