nsdocument nswindowcontroller nsviewcontroller - one xib? - cocoa

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.

Related

Cocoa OS X Bindings and Non-trivial data model

This project is to create an editor tool in Cocoa & Swift for Mac OS X that will edit a non-trivial data structure. A pared down schema looks like this:
Game
title : String
[ Room ]
Room
roomKey : String
roomName : String
roomDescription : String
[ Object ]
[ Exit ]
Object
objectDescription: String
Exit
destinationRoomKey : String
sourceRoomKey : String
The current implementation - the third go-around - has a single Document.xib file (the app is document based) and in that I'm hooking up a NSObjectController to the base game object loaded by the document, and an NSArrayController to the game objects array of rooms. There's an NSObjectController for the Room. I have not done the objects or exits yet.
The views are handled by a base root view controller, which swaps sub-views in and out as you go up and down the view hierarchy. On the view for the root game state, you click an "edit" button that slides in the table view for the list of rooms. Clicking a button in one of the room rows slides in a room detail view which has its own controller.
This is all working well enough. I have hooked up the object controller of the room so that it gets the selected room of the rooms array as its object, using Interface Builder bindings. I can do this because I have all the views, view controllers and data model controllers in the one XIB file.
However: now I am adding the game objects to this mix and the XIB file is getting very unwieldy. I really feel like I want to do this in separate XIB files, but when I tried that previously I was not able to hook up the controllers to each other. I tried manually writing code to load & save the data at the same time as the controllers had their view displayed and removed but this was flakey and error prone. So far the most elegant and robust result I have had is with this one XIB approach.
I looked at the programmatic API for binding but could not understand how to get it to work, or how to discover what the key path would look like. I suppose if it was possible to do the bindings programatically you could put the different parts in different XIB's and do the bindings at load time. But I could not find any examples of anyone doing that successfully and it seemed a road to madness.
At present I'm having no problems with Swift and its relations to Cocoa and Objective-C so if anyone has answers in Objective-C or Swift I'd be happy to hear them. I have not put Swift as a key word for this question as its not part of the problem.
I've seen the StackOverflow answer about hierarchical models, and its what I'm currently doing, so it doesn't help. The problem is that this approach gets unwieldy when there's several layers of master-detail.
I've also seen the StackOverflow answer about sharing controllers, and it was what I tried before and where I ran into the issue described there, that if you specify a controller object in a NIB it will get instantiated as an independent object. Hence why I have the huge-mega-NIB-of-death approach at present.
I could make the title of this question "cannot make programmatic bindings work" but I'm not sure that that is the right approach anyway.
Surely someone has done the job of making a non-trivial data model work with Cocoa before?
Your secondary NIBs should be view NIBs, their owners would be an instance of NSViewController or a custom subclass. That has a representedObject property. The NIB and its view controller class should be thought of as stand-alone, theoretically-reusable components. That is, in theory, that NIB could be used in multiple contexts to represent a particular kind of object. So, you should typically not want connections to other parts of your UI or their controllers, other than knowing what object this view is being loaded to represent.
Within the NIB, you can either bind to the File's Owner with a model key path that goes through representedObject or add an NSObjectController that binds to File's Owner's representedObject and then bind your views through that with controller key selection.
When you load such a secondary NIB, you would have to set its representedObject to the object it's supposed to represent, taken from the array controller's selection. This should be done in code, presumably the same code that decides it needs to load the NIB and does so.
If the design of your UI is such that a detail view needs to trigger a behavior that's best handled at a higher level โ€” for example, a Room view needs to arrange for an Exit view be slid into the window, but not as a subview of its own view โ€” the detail view controller should define a delegate protocol and implement a delegate property. For example, the Room view controller's delegate protocol might have a method -roomViewDidChangeSelectedExit:. The Room view controller would call that on its delegate, passing self. You would set some coordinating controller (perhaps the window controller) as the the detail view's delegate.
It's not clear to me if the "detail" views and the "master" views are visible simultaneously. That is, can the user change the object that the detail view is meant to show without backing up first? If so, there are a couple of approaches.
You could set up the bindings programmatically when the view is loaded. This would be the responsibility of the controller that loaded the detail view. It's not the responsibility of the detail view's controller. That doesn't have the higher-level perspective and knowledge to set up the binding. Anyway, you could do it like:
[detailViewController bind:#"representedObject" toObject:self.arrayController withKeyPath:#"selectedObjects.firstObject" options:#{ }];
Be sure to call -unbind: before the detail view controller is released.
The other way to do it is to simply observe the changed selection using a non-Bindings approach, and set the new representedObject in the code that gets triggered. For example, if your master view lets the user select an item in a table view, you would set up the table view's delegate (almost certainly already done) and implement -tableViewSelectionDidChange:. In that delegate method, query the newly-selected item and assign it to detailViewController.representedObject.

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.

How can I directly respond to NSTableView edits while still using NSArrayController?

In my Cocoa app, I have a sheet with a one-column NSTableView that lists a bunch of files in a directory (the app makes back-ups of it's main database, provides this list to users so they can revert to a particular back-up). The content is loaded into and provided to the table view by an NSArrayController, each object is just an NSFileWrapper (I'm considering using NSURL instead, but I digress). The NSArrayController handles sorting, enabling the buttons when a row is selected via bindings, that's all great. I have an NSWindowController subclass object (BackupsSheetController) that hooks all this up and exists in the sheet's nib.
However, when a user edits one of the cells, I want to respond to that change from BackupsSheetController by appropriately re-naming the file represented by that cell, putting it in its new location. Since the table view is bound to the NSArrayController, I don't get sent the NSTableViewDataSource message โ€“ tableView:setObjectValue:forTableColumn:row:. If I set my BackupsSheetController as the datasource for the NSTableView object in the nib, I get sent that message sometimes, but not very often, to say nothing of every time.
Most questions and examples I see out there for this scenario handle this all by using a custom model class for items in their table view, and make some controller object an observer for changing properties that they wish to respond to. In other words, each item would be something like a BackupNode object, and BackupsSheetController would observe each for changes to the name property (or whatever I would call it). That seems totally overkill for my scenario, but I also don't want to ditch the bindings I've already got in use and I don't see another way to do this. Is there another way to do this, to make sure I reliably get the setObject:... message? Or should I drop the NSArrayController and make BackupsSheetController the delegate and datasource for the table?
In the "BackupNode" scenario, I don't see why BackupsSheetController would observe each for changes in its name. That's a very roundabout way of doing things. I would think that the hypothetical BackupNode object would simply do the necessary work in its setter for the name property.
Anyway, I recommend using proper model objects. When you try to build a model with only Cocoa-provided objects like NSFileWrapper, NSURL, or NSMutableDictionary, you end up doing more work in the long run than if you just make a proper model object.
On a tangential topic, why is your window controller in the NIB? It should be the thing which loads (and owns) the NIB, which of course requires that it exist prior to the NIB being loaded, which means it can't be instantiated in the NIB.

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.

Why do UIViewControllers have xib files and UIViews do not?

When I create a new UIViewController in xcode, it offers to make me an associated nib for the interface. However, when I create a UIView, it does not. If my understanding of MVC is correct, views should really be the parts that contain the interface elements (i.e. the nib) while view controllers are the parts that hook the functionality to the views they control.
I'm sure I'll be able to get it working together either way, so this is more of an exploratory question.
This seems like a case where I'm missing some fundamental understanding of how these two parts should be used. What am I missing?
Because UIView is usually not used in such way.
However How do I associate a nib (.xib) file with a UIView?
The answer I eventually got that satisfied my interest was roughly this:
The job of a view controller is to manage a view hierarchy. Interface Builder is an excellent tool for creating view hierarchies. Xcode offers to create a .xib when you create a new view controller because chances are high that you'll want to load the controllers' views from a .xib.
.xib files aren't necessarily meant to hold every UIView (or subclass) that goes into the view, just a general outline of views that don't change during the life of the view. The other UIViews are much easier to create programmatically since they change often.
I had a similar confusion. Basically (according to the MVC) the View is contained inside the Controller. In the iPhone this means that UIViewController has a property called 'view' which is a UIView instance.
A XIB file (and this is not mentioned often) is a serialised UIView instance. It is roughly an XML sub format which represents a UIView with all its subsequent views. So when you create a UIViewController, a UIView is created in the form of a XIB and bounded to that controller.
A new UIView therefore does not have a XIB because they are essentially the same thing...

Resources