PDA

View Full Version : Help binding across .xibs in master/detail app




GorillaPaws
Jun 22, 2012, 05:00 PM
I'm building an iTunes Style app backed by core data. Here is a somewhat simplified version of the classes and the .xib files (in the MainWindow there is a splitviews, a NSToolbar, some tabless NSTabViews, and placeholder views that get replaced at launch, but for discussion purposes this is is the relevant object graph):

http://dl.dropbox.com/u/6679821/Master%3ADetail%20Class%3Axib%20diagram.png

The MainWindowController loads in the detailViews via tabless tabviews, and within the particular detailView.xib I'm working on is an NSTableView that I would like to populate via bindings, based on the current selection in the source list. My detail view has an NSArrayController in entity mode that's ready to wire up to the table, but I'm not sure how to grab the reference to the current selection in the other .xib in a way that preserves loose coupling and good class design.

I've built tutorial apps that can handle what I'm trying to do when everything is in the same .xib, but I'm trying to observe good object oriented design practices with this app, and have broken things into separate chunks for expandability, maintainability, and reusability. What is the most correct way of inter-class communication in this situation?



Sydde
Jun 23, 2012, 12:36 PM
Forgive me for responding without having Core Data knowledge, perhaps there is a subtlety in Core Data that is beyond me. I think you are over-thinking this. xibs are discreet, total entities that resist interconnection without coding. A window and its controller are usually seen to own their views, so you should probably combine this structure into one xib. You can instantiate the SourceList and DetailView controllers as objects within the MainWindowController xib and link them to their views instantiated in the window. This is the most straightforward method. If you were planning on having an alternate view (e.g., that pops up when a list item is selected), you could instantiate that view and its controller in the window's xib as well.

chown33
Jun 23, 2012, 01:59 PM
... I'm not sure how to grab the reference to the current selection in the other .xib in a way that preserves loose coupling and good class design.

Why do you want or need loose coupling? How many alternate sub-views and sub-xibs and sub-controllers do you expect to have?

I'm siding with Sydde on this, absent more information. Unless there's an insurmountable technical reason that Core Data prevents you from having a single integrated controller and xib and view here, I think you've over-factored it, perhaps with a rationale for "future flexibility", which makes me think YAGNI (http://en.wikipedia.org/wiki/You_ain%27t_gonna_need_it).


If I absolutely had to get a reference to the current selection, then surely that's something that a sub-controller should be able to provide, because it would have to be part of its contract with a super-controller, who seems to have a need for getting a reference to the current selection.

The main point here is the super-controller shouldn't have to be aware of a sub-controller's private views. It should address all its needs and interests to the controller responsible for the view. Otherwise you're defeating some of the purpose in having a controller, which is to promote looser coupling between the M, the V, and the C.

What should the reference be that indicates the current selection? I don't know, and can't tell from the diagrams. However, it should be as abstract as is reasonable for the objects involved. For example, it might be an NSIndexSet or an NSIndexPath, or even a keypath, rather than some subview object taken directly from the nib.


I also think you might be missing something by calling it "inter-class communication". It's not. It's inter-object communication. One object has knowledge of another object, which it wishes to communicate with. How would you usually do that? Key-value coding? Direct message sends to a specific method? Notifications?

Clearly, the super-controller already has knowledge of the sub-controller objects, so how do they communicate now? You should think of it as a graph of objects ("graph" in the CS sense), which have knowledge of one another in some way, then work out how they interact (communicate) using all approaches at your disposal (KVC, direct message sends, etc.).

GorillaPaws
Jun 23, 2012, 09:42 PM
A window and its controller are usually seen to own their views...

I was under the impression that views should be owned by thier respective NSViewControllers, which themselves are managed by their NSWindowController. I could absolutely be wrong about this though. When is it appropriate to break things into sub-nibs?

Why do you want or need loose coupling?

This is my first stab at a "real" OSX app, and I'm mostly doing this project to learn how to make these shoebox-style apps. I'm trying to use best practices, and good OOP design practices so I can use the architecture for this app as a kind of basic "skeleton" for future projects. One of my architecture design goals is to avoid "god classes" and to delegate responsibility so that each class manages just the necessesary and sufficient behavior for it's particular role. I certainly don't need loose coupling, but I'm hoping to follow the best practices and use the appropriate amount of coupling so I can avoid getting into bad habits. This may very well be a situation where I've over-factored the design. Regarding coupling, would it be "wrong" for the detail view controllers to know about the source list? I'm under the impression that this is the Main Window Controller's responsibility to pass out this info on a "need to know basis."

I've avoided adding details mostly because I was hoping that by keeping my question generic, it would be more broadly helpful to others trying to do similar things. Additionally, I was trying to provide sufficient detail, without muddying the waters with information not critical to understanding my basic problem. At this point, I think it makes sense to be more specific with the details.

In my sourcelist I have several permanent groups that correspond to different types of collections. I have a NSManagedObject subclass called a SourceListNode which has certain properties related to it's position in the index, the value of it's badge (e.g. the little pill with a number on the righthand side of folders in the mail.app), a bool for if it's a root object in the sourcelist, a name, it's group type, etc. I have several classes inheriting from the SourceListNode, each corresponding to a different grouping in the source list (using iTunes as an example these would be: Library, Store, Devices, Genius, and Playlists). In these subclasses, I'm implementing the specfic properties/behavior related to that grouping type's particular problem domain. At the moment, I've only implemented 2 of these groups, because I want to build the basic class architecture with the minimum amount of complexity. I fully intend to add the additional classes soon, I just want to get the smallest possible set of functionality working first, then it should be fairly trivial to implement the other groups using the implementation of the working ones as a kind of "template".

I originally had the sourcelist in the main window's xib, but I ran into issues with it being a view-based tableview. I can't recall the exact specifics, but it involved problems when the data source/delegate of the NSOutlineView wasn't the file's owner.

In the detail views, I'm swapping them based on the group type of the current selection via a tabless NSTabView. The goal is to have 3 different views showing the same content of the selected sourcelist item in different ways (table, collectionview, and coverflow) for some of the group types and have just one view for other group types. For the Detail Views implemented with 3 swapable views, the Detail View controller, will handle the logic of swaping out between the table, collectionview, and coverflow. Based on the selection in the table in the detail view, the lower half (it has a horizontal splitview) will show the item view with all of the selected item's detials. At the moment, I'm just focusing on getting the tableview working for the detail view for one of my group types. There may be some aspect of YAGNI here, but this is all included in the 1.0 vision. With all of the planned complexity, I'm having a hard time seeing how this could all be done, and properly organized using best practices in a single xib. I'm trying hard to break my problem down into it's logical components so I can work through each piece independantly. I'm also hoping to set things up so that once I figure out how to implement the interactions for one view, it should be fairly easy to replicate it for others (needing only to make tweaks for each particular problem domain).

In my search for answers to my original question here late last night I re-discovered this great article by Martin Pilkington called "Building a Modern Cocoa App (http://www.mcubedsw.com/blog/index.php/site/comments/building_a_modern_cocoa_app)". It's from back in 2010, and it made a lot more sense to me now, since I'm trying to work through some of the problems his solution is addressing. In a way, his post kind of validated my designs and made me feel a bit less crazy last night. In my current implementation, I'm tracking the source list's selection via an NSNotification in the Main Window Controller. When this fires off, the Main window Controller asks the Source List Controller for the selected object (which will be a subclass of my SourceListNode class). Based on the "type" property in the selected node, I swap the detail view, and pass a reference to the selected object to the detail view's controller. The detail view's controller stores this reference as a property and has an NSObjectController (I borrowed this idea from the Pilkington's article) in the detail view's xib that has it's content bound to this property. From here, the NSArrayController controlling the tableview in the detail xib can bind it's values based on the NSObjectController. At least in theory. I still haven't gotten it working as expected because I'm having some KVC issues with my keypaths, and I'm not completely sure about exaclty what values I should be using in the "controller key" in the bindings tab, and figuring out if my NSObjectController should be in entity or class mode. That and a pre-released version of an unnamed IDE (that I can't talk about publicly) has given me a major setback.

Does this seem like a reasonable design? I'm not an expert like you guys, and my main goal for this project is to learn, put a lot of the things I've been reading about into practice and grow, so your feedback and advice are really invaluable and greatly appreciated. Sorry for the long reply, I tried to keep things as focused as possible. Hopefully my explanation of the arcitecture was clear enough to be comprehended.

Sydde
Jun 23, 2012, 11:38 PM
I was under the impression that views should be owned by thier respective NSViewControllers, which themselves are managed by their NSWindowController. I could absolutely be wrong about this though. When is it appropriate to break things into sub-nibs?

There is no such thing as a "sub-nib". You lay out an entire interface in a single nib. View controllers are instantiated in the nib by dragging the NSObject element into the nib list and setting its class with the inspector (or however it works in XCode 4 now). The controllers can then be seen to own their view, assuming the outlet has the right characteristics. There is not a way to make preset connections between nibs, because nib loading does not propagate other nib loading. Only objects referenced within the nib can be directly connected to each other.

Typically, you create a nib for any document-like entity in your app that needs a specific interface. It is not uncommon to have a separate nib for preferences, or nibs for various dialogs, or for things the user does outside the bounds of a document (say, examining cookies).

Really, putting a nib together is a pretty quick operation (though, if you leave out a connection, your app could just lie there inert). Coding and debugging objects tends to be more work, but once you have stable code, transplanting it to a different nib tends to be fairly trivial. In fact, I think the real work code (the Model part) that you might want to reuse is very often not even represented in a nib.

Just arrange your nib the way you want your window to look, make sure you have the resizing masks right, and get to doing the actual work. If this is partially a learning experience, learn from it. Put your portability in .h and .m, treat nibs as mostly non-reusable.