Updates from April, 2008 Toggle Comment Threads | Keyboard Shortcuts

  • Jonathan Dann 22:09 on April 22, 2008 Permalink | Reply  

    I’m on the Mac Developer Network! 

    I recently did a post on NSViewController with Cathy Shive, and I realised today that the post was link to on the MacDevNet (Thanks Scotty!). I’m actually feeling like a developer for once.

    Welcome all, nice to see you. Sit down, have a cup of coffee (espresso is served here), and I’ll write some more in a bit.

     
  • Jonathan Dann 14:32 on April 6, 2008 Permalink | Reply  

    Using NSTreeController 

    A common UI concept in Mac development is that of the source list, which everybody knows from iTunes, iPhoto, etc. To do this, NSTreeController can come in particular handy, although it can come with a bit of a headache as the API is somewhat lacking and a proper model is essential to making this easy to use.  I’m going to go over the solution that’s worked for me.
    (More …)

     
    • Michael 23:08 on June 3, 2008 Permalink | Reply

      Hey Jonathan,

      great post, a quick question — in the XSControllers pattern you recently described with KATI, where do you put your NSTreeController? In the main nib file of the document window or in the sub-nib containing the actual controller client?

    • Jonathan Dann 23:31 on June 3, 2008 Permalink | Reply

      (sorry about the code formatting here, will change my style sheet sometime)

      That’s a good question actually, and is a little more involved than it sounds. I’ve changed it around quite a bit but have finally settled on a setup.

      I’ve put it in a nib of an NSOutlineView controller, the outlinve view is the left side of an NSSplitView and so it’s in the nib of the child controller of the root view controller in the tree. This makes it a couple of levels away from my model controller – my NSPersistentDocument. The problem then comes when I want to insert a node in my tree from code in the persistent document, which is often the case as its where I do the dealings with the model.

      To this end I’ve set the representedObject of all the view controllers to the NSPersistentDocument instance and then I can bind the tree controller to @”File’sOwner.representedObject.managedObjectContext”. To get at the tree controller from the document I’ve made a method which I added to XSWindowController:

      • (XSViewController *)controllerForNibName:(NSString *)name;

      {
      NSPredicate *predicate = [NSPredicate predicateWithFormat:@"nibName like %@",name];
      return [[[self flatViewControllers] filteredArrayUsingPredicate:predicate] objectAtIndex:0];
      }

      and this calls

      • (NSArray *)flatViewControllers;

      {
      NSMutableArray *flatViewControllers = [NSMutableArray array];
      for (XSViewController *viewController in self.viewControllers) { // flatten the view controllers into an array
      [flatViewControllers addObject:viewController];
      [flatViewControllers addObjectsFromArray:[viewController descendants]];
      }
      return [[flatViewControllers copy] autorelease];
      }

      which is just the same depth-search in the -patchResponderChain method, just factored out.

      So when I need the tree controller from the persistent document I can call

      • (XSWindowController *)mainWindowController;

      {
      return [[[self windowControllers] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"windowNibName LIKE %@",ESDocumentWindowNibName]] firstObject];
      }

      • (ESOutlineView *)projectContentView;

      {
      return [[[self mainWindowController] controllerForNibName:ESHorizontalSplitViewNibName] valueForKey:@"projectContentView"];
      }

      • (NSTreeController *)treeContentController;

      {
      return [[[self projectContentView] infoForBinding:@"content"] valueForKey:NSObservedObjectKey];
      }

      (I show them all as I’ve just copied and pasted from XCode you can obviously condense these methods, but I use them all separately, the projectContentView is the outline view bound to the tree controller and ESDocumentWindowNibName is just an NSString *).

      This works for me as I need to access the tree controller more often from the view controller (for outline view delegate methods) than in the persistent document. If I were to have it the other way round I’d programmatically create the NSTreeController in the persistent document and then bind to it (via representedObject) from the nib, but in the view controller I’d still need to get at is somehow.

      With this setup I can’t seem to decide on the best setup that requires the least convoluted access to the controller from either side (the document or the view controller). I did think recently of putting it back in the document and then creating an NSTreeController ivar in the view controller which I then bind to the tree controller, but its *very* convenient having it in the nib.

      I hope that I haven’t confused the matter! If you come up with a better encapsulated solution I’d like to hear it.

      Thanks for reading, glad the code’s useful.

    • Michael 18:56 on June 4, 2008 Permalink | Reply

      Thanks for the explanation – I was expecting something in the lines of that but hoped that maybe you’ve got some other smart solution ;)

      I just recently started to look seriously into bindings, but after fighting with this and similiar design problems I came to think that bindings are awesome & great… for doing simple things. I’d love to be proven wrong but right now it seems to me that when trying to solve complex problems with bindings you end up with architecture which (indeed) contains much less code but is actually much more complex and time-consuming to maintain and develop.

      That especially applies to using bindings with NS*Controller classes (as opposed to using simple bindings to custom controllers for field value/state tracking).

      Anyways, just an opinion.

    • Jonathan Dann 19:07 on June 4, 2008 Permalink | Reply

      You’re welcome, I’m still sticking with with bindings though. As my project has grown they’ve become more and more indispensable. There are some architectural considerations to overcome but the problems pale in comparison to doing it all with glue code.

      The only issue I have with the setup I’ve described it the drilling through relationships to get to the controller, but there are worse things.

    • Michael 19:15 on June 4, 2008 Permalink | Reply

      BTW, in the “drilling” solution you described, does it solve the problem of accessing the NSTreeController from another view? I mean, not the OutlineView, not the Document itself but some other sub-view which needs to also fetch data from the tree controller or access it’s selection?

      In particular, I mean a situation where you can use this (shared) NSTreeController from the Interface Builder.

    • Jonathan Dann 19:51 on June 4, 2008 Permalink | Reply

      Ah I see what you mean, in my setup all the view controllers have the same representedObject (the document) so they can get it that way, or they use controllerForNibName and get the view controller that owns the nib the tree controller is in, the tree controller is then hooked up to an IBOutlet.

      The outline view itself gets can get the tree controller too if it needs to like this:

      • (NSTreeController *)boundTreeController;

      {
      return [[[self outlineTableColumn] infoForBinding:@”value”] valueForKey:NSObservedObjectKey];
      }

      So in IB a view can use the keypath @”File’sOwner.representedObject.treeController” as long as the nib is owned by a view controller. If the view is in the windowController’s nib its just as easy @”File’sOwner.document.treeController”. An additional window (like an inspector) still references the same document. I haven’t had a problem with it yet.

    • Michael 21:15 on June 4, 2008 Permalink | Reply

      Hmm, could be I’m missing something very obvious but, how do you actually bind to that controller in the IB? I mean, yeah, you can get the controller using some kind of “File’sOwner” path but, from the pov of the IB, it doesn’t see it as a TreeController… does it?

      As far as I can see, you can use that keypath in a binding, but then you can’t select the “Controller Key” etc.

      BTW, coming back to your original article, one note – on the created NSTreeController in the IB you need to set the “Children” to @”children”, otherwise you’ll get “Cocoa Bindings: Cannot perform operation if childrenKeyPath is nil.” error.

    • Jonathan Dann 21:29 on June 4, 2008 Permalink | Reply

      Yeah sorry I forgot to put that in, also set the Leaf key path to @”isLeaf”. I’ll update the post.

      I see your problem, can you not append arrangedObjects to the keyPath? The controller key is just a convenient shortcut anyway.

    • Michael 21:52 on June 4, 2008 Permalink | Reply

      > I see your problem, can you not append arrangedObjects to the keyPath? The controller key is just a convenient shortcut anyway.

      Hmm… but if you use “File’sOwner.treeController.arrangedObjects” than you have no way of controlling what’s the object list and what’s the actual value. In other words, the real path is: “File’sOwner.treeController.arrangedObjects.nodeName” or rather: “”File’sOwner.treeController.arrangedObjects(each).nodeName”. But without the Controller Key you have no way of describing it – what’s the object list (array) and what’s the path to the individual object property to be used ie. in a given cell.

    • Jonathan Dann 21:57 on June 4, 2008 Permalink | Reply

      I’m afraid I’m not following, sorry. If you’re on leopard I can iChat screen share and we can try and sort it out, either that or you can email me your XCode project?

      If not then can you tell me what you’re trying to to and I might be able to help.

    • Jonathan Dann 00:20 on June 6, 2008 Permalink | Reply

      @”File’sOwner.treeController.arrangedObjects.nodeName” will return an NSArray of node names. The same happens when you have an NSArray and you call [myarray valueForKey:@"key"] the returned array contains the values for those keys.

    • Michael 18:50 on June 6, 2008 Permalink | Reply

      Ah, indeed, now everything makes sense. Thanks for your patience, that explains a lot.

    • Jonathan Dann 17:30 on June 7, 2008 Permalink | Reply

      Not at all, I wrote this to help people!

    • Jakob Dam Jensen 16:15 on July 5, 2008 Permalink | Reply

      Thank you so much for this Jonathan – I’ve been trying to get something to work for the last two days, and somehow it all makes sense to me now – thanks to you.

      Take care…

    • Ralph 19:35 on July 5, 2008 Permalink | Reply

      Hey Jonathan,

      do you have a sample Application which combines your ESNode and XSController?

    • Jonathan Dann 20:27 on July 5, 2008 Permalink | Reply

      @ Ralph,

      Sorry I’m afraid I don’t have anything prepared. The example app on katidev uses an NSOutlineView, so I’d just create an NSTreeController in that NIB and connect it up as I’ve described in this article.

      Jon

    • Jonathan Dann 20:28 on July 5, 2008 Permalink | Reply

      @Ralph

      Sorry I’m afraid I don’t have anything prepared. The example app on katidev uses an NSOutlineView, so I’d just create an NSTreeController in that NIB and connect it up as I’ve described in this article.

      Jon

    • Joseph Crawford 04:22 on January 20, 2009 Permalink | Reply

      This is a very good explanation and has surely helped me understand this a bit better.

      I am wondering if there is a benefit to using a tree controller rather than just handling the NSOutlineView groups/leafs using the objects.

      What does binding bring to the table that not using them doesn’t?

    • Jonathan Dann 19:11 on January 20, 2009 Permalink | Reply

      Hi Joseph,

      Glad it helped. As for the benefit, it’s mainly to help you ensure that changes to the model are automatically pushed to the tree controller without you having to intervene or tell the outline view to reload data, saving you the maintenance of the application logic in, what is likely, a fairly integral part of your app. Each time you add a node object to the managed object context (in the full-blown core data version I’ve also got on this blog) the tree controller will update its display.

      It doesn’t come without its caveats, which were the main reason for writing this post. When I wrote it there were no decent tutorials for the Leopard world that included the new NSTreeNode, which I saw as the saving grace of this whole setup.

      You save yourself writing the datasource methods, which is a both a blessing (not having to write them) and a curse (having to deal with the tree controller’s representation of things).

      So really my answer, is “not much, until I’d written all this” but it was a challenge to get it working. What’s probably more important are the extensions on NSTreeNode themselves, which allow to to properly navigate the content of the tree.

      Bindings with NSTreeController also allow you to remove a lot of other code, for example that which needs to know when the selected objects in the tree change. Without the tree controller you have to rely on notifications, which (in my experiments) are only sent for user-side updates. If you use bindings or simple KVO on the NSTreeController’s selectedIndexPath property then you save yourself some debugging hassle.

      Have you tried using table view’s with and without NSArrayController? You see the same reduction in code when you do adopt bindings in those cases, too/

      Sorry if this has been a bit waffling, I’m way too tired.

      Jon

    • Ernest 16:32 on February 19, 2009 Permalink | Reply

      Jonathan, thank you so much for this example. I have successfully implemented it into my project, but now I have a question for you. Maybe I am just not doing something right, but here it is: I added an Outline View to my window, which comes with the standard NSScrollView -> NSOutlineView -> NSTableColumn -> NSTextFieldCell. This works perfectly with my Tree Controller. However, when I change the NSTextFieldCell to use the check box cell (NSButtonCell) and run the application I get nothing but “Check” for my list? Am I missing something with the node in which I have to change to support NSButtonCell vs NSTextFieldCell?

    • Jonathan 09:39 on February 24, 2009 Permalink | Reply

      Hi Ernest,

      There could be an issue with incorrect bindings. What I did in my example was to bind the NSValueBinding (“Value” in IB) of the table column to the tree controller’s arrangedObjects.displayName keyPath. My first thought is that, as the NSValueBinding of the NSButtonCell refers to the on/off state of the button, the binding is turning them on if the bound string is not nil.

      Not sure how you would solve this one as there are two values you need to consider here, the value and the label. This should be possible with bindings, but I’d do a long search on the cocoa-dev mailing list from Apple, this kind of thing comes up regularly.

    • Elise van Looij 14:44 on March 14, 2009 Permalink | Reply

      Ernest, you need to set the Value Transformer (in IB under the Value property) on your NSButtonCell, NSNegateBoolean, if I’m not mistaken and if the value of the cell is an integer.

    • Matthias 13:22 on June 19, 2009 Permalink | Reply

      Jonathan, thanks for the article. In the groupDescendants method, shouldn’t this line:

      [groupsArray addObject:[node groupDescendants]];

      rather read:

      [groupsArray addObjectsFromArray:[node groupDescendants]];

      Thanks, M.

    • Paul 11:41 on May 23, 2010 Permalink | Reply

      I found the code I needed on Wil Shipley’s blog. Thanks for the link!

c
compose new post
j
next post/next comment
k
previous post/previous comment
r
reply
e
edit
o
show/hide comments
t
go to top
l
go to login
h
show/hide help
shift + esc
cancel
Follow

Get every new post delivered to your Inbox.