Recent Updates RSS Toggle Comment Threads | Keyboard Shortcuts

  • Jonathan Dann 20:53 on September 28, 2009 Permalink | Reply  

    Grand Central Dispatch – The Sleeping Barber Problem 

    I’ve written a new post on the Sofa blog about solving the ’sleeping barber problem’ using Grand Central Dispatch and block on Snow Leopard. Check it out here: http://www.madebysofa.com/#blog/the_sleeping_barber

     
  • Jonathan Dann 22:10 on September 9, 2009 Permalink | Reply  

    Forget the Code 

    Along with the rest of the world, I’ve been coding on Snow Leopard recently. Blocks are a great addition to the C language, irrespective of their use in Apple’s Grand Central Dispatch APIs. I’ve found one usage-pattern where they save me time (I have little to spare), reduce the amount of mundane monkey-work I have to do , and make my code more reliable. In many places blocks can save me writing a whole line of code.

    Transaction-based APIs

    Cocoa is full of APIs that cause the developer to start a transaction, do some work and then end the transaction. Take for example drawing something with a shadow in an NSView:

    
    - (void)drawRect:(NSRect)rect;
    {
        [[NSColor whiteColor] setFill];
        NSRectFill([self bounds]);
        [NSGraphicsContext saveGraphicsState];
        [[self shadow] set];
        [[NSColor redColor] setFill];
        [[NSBezierPath bezierPathWithRect:NSInsetRect([self bounds], 10, 10)] fill];
        [NSGraphicsContext restoreGraphicsState];
        // do some more drawing
    }
    

    The important part of the above code is between the calls to save and restore the graphics state. Quartz (the underlying drawing API) is a state-machine, so if we were to set the shadow on our existing context, all subsequent drawing would be performed with said shadow. By saving the state, we can set a shadow temporarily and the continue to draw without a shadow by restoring the graphics context to its eariler state. It doesn’t stop with shadows, though; there are a multitude of setting we can apply to our contexts.

    If you forget to call +[NSGraphicsContext restoreGraphicsState] after saving a state though then that makes me a sad panda. Try flipping the context’s transformation matrix and not restoring your state. You won’t win any design awards.

    We have similar issues if we are manually notifying key-value observers of changes. All calls to -[NSObject willChangeValueForKey:] must be bracketed with calls to -[NSObject didChangeValueForKey:].

    It doesn’t stop there, if you’ve done any work with Core Animation, its not uncommon to want to disable the implicit animations of layers, or to make a group or animations with longer durations than default. In these cases your code is littered with:

    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    // change any number of properties on your CALayers
    [CATranaction commit];
    This gets old, very quickly.
    

    Enter Blocks

    Lets make our code more robust, we all like robust. Blocks are anonymous functions, we can write them in-line and the can capture variables from their enclosing scope. In real-person terms we can write methods like this:

    @implementation NSGraphicsContext (ESAdditions)
    + (NSGraphicsContext *)transactionUsingBlock:(void (^)(NSGraphicsContext * /* currentContext */))block;
    {
        [self saveGraphicsState];
        block([self currentContext]);
        [self restoreGraphicsState];
    }
    @end

    In our app we can then do things like this:

    - (void)drawRect:(NSRect)rect;
    {
        [[NSColor whiteColor] setFill];
        NSRectFill([self bounds]);
        [NSGraphicsContext transactionUsingBlock:^(NSGraphicsContext *currentContext){
            [[self shadow] set];
            [[NSColor redColor] setFill];
            [[NSBezierPath bezierPathWithRect:NSInsetRect([self bounds], 10, 10)] fill];
        }];
        // do some more drawing
    } 

    We’ve now done some drawing with a shadow, and we’ve not had to write those pesky calls to +[NSGraphicsContext saveGraphicsState] and +[NSGraphicsContext restoreGraphicsState].

    Readability++

    The wonderful side-effect of this is that the code executed as part of the block is indented by Xcode, too. Which means we can see, at a glance, the scope to which our transaction applies. Imagine doing the same for drawing into a CGLayer, your drawing code is in the block, and it all gets rendered into the layer. Blocks help you expresses your intent. (Yes, that’s a loaded statement. Feel free to quote me on this.).

    You can download my categories and functions here. With a shiny New BSD license. Enclosed I have categories on:

    • NSGraphicsContext
    • CGContextRef
    • NSManagedObject (for writing your custom accessors)
    • NSObject (for KVO)
    • CATransaction
    • NSAnimationContext
    • NSTextStorage

    Mutexes and semaphores would also be a great candidate for this. You can easily write your own equivalent of @synchronized, to work directly with pthread mutexes.

     
  • Jonathan Dann 18:07 on October 24, 2008 Permalink | Reply  

    I Love Outline Views – Here’s Mine 

    Apps like Coda are the de-facto standard for good user-interface design nowadays. You can do a lot with the standard Cocoa controls provided by Apple, but you can’t always get exactly what you want out-of-the-box. For example, the CSS edit section of Coda has a fantastic way of dividing tasks: the animated outline view.

    Rather than having a monolithic view that has all the controls grouped with dividing lines; each section, “Text”, “Colors and Background”, “Dimensions”, all exist in their own collapsible view. Clicking the separating bar animatedly expands or collapses the view.


    To make one of these views isn’t quite as easy as you first think, and you can quickly go down the wrong road in the design. The super-secret trick is to *not* use Core Animation, or more precisely: ignore the lure of NSViews conformance to the NSAnimatablePropertyContainer protocol. As of Mac OS X 10.5, a few of the properties of a view or window can be animated simply be replacing calls like [view setFrame:newFrame]; with [[view animator] setFrame:newFrame];. This is fine when you have one or two views that you want to move but it’s impossible to coordinate the movements of multiple view using this API. After using Core Animation, one would expect that after a call such as [[view animator] setFrame:newFrame]; requesting the frame from the view would return newFrame. Unfortunatley it returns the original frame of the view. Only when the animation is done, do you get newFrame. Furthermore, just try setting the delegate of the CABasicAnimation object that handles the implicit animation and getting any form of useful information back. You can’t. You’re in for a world of pain if you try.

    The solution: use good-ol’ NSViewAnimation. That way you can create all the dictionaries containing the animations for all the views you want to, then instantiate one NSViewAnimation object to handle the lot and set them off at the same time. Simple.

    The Animating Outline View

    The outline view is made up of a few classes, the TLDisclosureBar, a subclass of the highly-configurable TLGradientView; the TLCollapsibleView which has a TLDisclosureBar and any NSView you like as its subviews. A TLCollapsibleView itself can be used alone, and will animate the collapse/expansion of the NSView subview (which I term the “detail view”).

    The fun part comes when TLCollapsibleViews are subviews of a TLAnimatingOutlineView. In this case, the TLAnimatingOutlineView takes over all the animations, simply asking the TLCollapsibleView that the user has selected for the NSDictionary object that describes the collapse/expansion animation. With this information, it constructs the movement animations for all the subviews below the collapsing/expanding view and handles the required change of frame size to accommodate the changing content size. It’s all quite elegant, if I do say so myself.

    Not being content with example code, I’ve made sure these classes are real-world useful. This view is integral to BibTeX management in Scribbler so it needs to be good. Some of the relevant parts of the NSOutlineView API are replicated and the delegate of the TLAnimatingOutlineView gets will/did/expand/collapse notifications, along with allowing the delegate to deny/allow animations, too. Each of the TLCollapsibleViews will also ask their detail views if the collapse/expansion is allowed if the TLCollapsibleDetailView protocol is implemented. The TLAnimatingOutlineView itself works when in an NSScrollView.

    The code, along with an example project, is hosted on Google Code here. It is distributed under the new BSD license.

    So why is is not ESAnimatingOutlineView, why the TL namespace? Well, I’m working on something big, at the same time as writing Scribbler. Start guessing.

    Update

    I’ve fixed a few bugs in the code, the current version can be checked out of the repository. Fixes include: proper support for hiding subviews of the TLDisclosureBars when the outline view is resized small, a correction to the autoresizing mask of the TLAnimatingOutlineView itself, declaration of keys that the TLCollapsibleView supplies with the animation dictionaries, the delegate of the outline view and subclasses of TLGradientView are no longer unregistered for notifications that client code specifies (i.e. no longer uses [[NSNotifcationCenter defaultCenter] removeObserver:_delegate];).

     
    • Mark Aufflick 14:38 on October 25, 2008 Permalink | Reply

      So happy to hear about the bibtex support! Oh, the animated views will be nice too, and thanks for sharing the example code.

    • Jonathan Dann 17:52 on October 25, 2008 Permalink | Reply

      Hi Mark,

      Thanks for the encouragement! I’m getting really excited about this app. I just hope that I can get it out asap :)

      Glad you like the sample code too. If you do manage to use it, let me know.

    • Brad Gibbs 17:45 on January 14, 2009 Permalink | Reply

      Thanks for the sample code. It’s been very helpful!

      The sample project failed to compile initially. I changed a #import from Espresso/TLGradientView to TLGradientView and it compiled and ran fine.

      I’m trying to integrate your code into an app. I’ve copied and pasted the AppController class in your sample project to an NSViewController subclass in my project. The app compiles and runs, but crashes when I try to expand or collapse an item. The Debugger stops on this line:

      if (![(id)self.delegate respondsToSelector:@selector(outlineView:shouldCollapseItem:)])

      The sample code I downloaded gives a warning stating that AppController may not conform to the TLAnimatingOutlineViewDelegate protocol. I added to my outline view controller, which shuts up the compiler, but the app still crashes.

      Any thoughts on what I might be doing wrong?

    • Markus 23:45 on August 6, 2009 Permalink | Reply

      Great stuff. Downloaded yesterday and works perfectly. One minor issue is the delegate method names set in -setDelegate: of TLAnimatingOutlineView in TLAnimatingOutlineView.m

      In your version they’re all tied to -outlineViewItemWillExpand:, they should be wired to Did/Will Expand/Collapse. This is the fix:

      - (void)setDelegate:(id )delegate;
      {
      if (_delegate == delegate)
      return;
      [self _removeDelegateAsObserver];
      _delegate = delegate;

      if ([(id)_delegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
      [[NSNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(outlineViewItemWillExpand:) name:TLAnimatingOutlineViewItemWillExpandNotification object:self];
      if ([(id)_delegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
      [[NSNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(outlineViewItemDidExpand:) name:TLAnimatingOutlineViewItemDidExpandNotification object:self];
      if ([(id)_delegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
      [[NSNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(outlineViewItemWillCollapse:) name:TLAnimatingOutlineViewItemWillCollapseNotification object:self];
      if ([(id)_delegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
      [[NSNotificationCenter defaultCenter] addObserver:_delegate selector:@selector(outlineViewItemDidCollapse:) name:TLAnimatingOutlineViewItemDidCollapseNotification object:self];
      }

    • Jonathan 08:02 on August 7, 2009 Permalink | Reply

      Excellent, I’ll file it as a TODO :)

  • Jonathan Dann 00:00 on October 6, 2008 Permalink | Reply
    Tags: Core Image, Scribbler, vImage   

    Scribbler’s Image Editing Opensourced Part 1 

    Well since I no longer work at the NHS I’ve been working really hard on one of the great features of Scribbler, image editing. As something I wanted to put in at the very start, I was really enthusiastic to start using Apple’s Image Kit. It promised developers the chance to do very little work and still have fully-fledged image management, provided by IKImageBrowser (very good) and image editing thanks to IKImageView. Unfortunately, the latter does not live up to the hype. After battling with it for a while and trying to justify to myself that it would suffice for version 1.0 of Scribbler, it was just way too incomplete, buggy, and damn ugly to leave in. I try to work with the idea that if it bugs me, it’s not good enough.

    Image Loading

    Let me start with IKImageView. It loads images ok, although larger images aren’t loaded nicely: they’re drawn incrementally in a really jarring fashion, even if you pass the view a CGImageRef that’s already in memory.  That being said, it does handle quite a few different types of images, so we give it half a point.

    Image Editing

    The main function of such a view is not for viewing images, but for editing them within the application. Many users want to quickly change an image they’re working with and then get straight back work.  Taking our cues from iPhoto we guess that this editing will take the form of colour adjustments rotating and cropping. If we’re feeling really generous, then we can also include a straightening tool and some zooming (although the latter is relatively superfluous unless we have some sort of pixel-level editing like iPhoto’s red-eye removal or the touch-up tool).

    So we’ve put IKImageView in our app, and loaded an image, we then want to change the brightness and contrast, so we double-click on the image and open up the shared instance of the IKImageEditPanel (left).

      

    Compared to iPhoto’s panel (right), this is ridiculously ugly, has no icons, has an Aqua button at the bottom instead of a properly-rendered HUD button.  The biggest issue: it appears off-screen all the freaking time. Can you imagine editing a bunch of images and having to manually drag the panel to where you want it each time you open the damn thing up? No amount of setting the frame of the window or teaching it to autosave it’s position fix this, and even if we could, the first time you run the app it’s at the bottom of the screen with most of it hidden.

    Lastly, you just try setting a new image in the view and getting the edit panel to do anything anymore, as far as I can tell -reloadData is a no-op, it neither reloads the data for the panel, nor makes you more attractive. The only way to fix this is to close the panel, and reopen it (and then moving it so you can see the controls).

    Rotation

    If we want to rotate the image we (in code) select the IKToolModeRotate mode and we get our onscreen compass:

    The problem with this is two-fold: click on the image at about 3/4 the way up and the rotation compass is drawn outside the bounds of the view; secondly, the user interaction is all wrong. To rotate you click and hold ok the image and move the mouse around in a manner that’s so hard to control it’s useless. Then think about saving the image after a rotation. The view itself has no concept of a canvas, so we can’t save an angled image on a white background, and we can’t arbitrarily decide to ignore the user’s rotation setting an save an un-rotated image (rock – me – hard place). Basically the tool is broken and doesn’t provide useful functionality to users.

    Cropping

    Then we have IKToolModeCrop. “Fantastic!” I hear you cry. “No longer will my users have to open up another app to crop an image, and I won’t have to write that code”. You diligently select a region if the image, then call -setCurrentToolMode: and….

    Nothing. Nada. Zip.

    Reading through the programming guide tells us that Apple have declared the mode and not implemented it (apoordesignersayswhat). So we then have to do it, but can we get the selection rectangle? Of course we can’t! So that nice crop button we made goes to waste, unless you want to subclass, track the mouse, interrupt -drawRect:, apply the crop using core image (note to all that CICrop filter is not a magic bullet), draw a CGImage with the cropped version, then reset the image. No thanks. On it’s own that might be fine, but with all the other rubbish in this class you’re starting to flog a dead horse.

    So that’s why IKImageView will *not* be making an appearance in Scribbler, and if you find it in any other apps then… well… I heard that Windows Photo Viewer Deluxe Family Millennium Media Edition is fantastic.

     
    • David Allen Parizek Jr 20:59 on February 28, 2009 Permalink | Reply

      So what did you turn to instead?

      I am finding I have the exact same dilemma. I am more of a newbie though, and was now attempting to figure out how to do it elsewise by reading Programming with Quartz (Glephman, Laden) to get a clue and then thought I would go hunt for a Cocoa open source project I could mimic code from.

    • David Allen Parizek Jr 21:01 on February 28, 2009 Permalink | Reply

      What is vImage? It is mentioned as a tag, but their are no other posts on this site tagged that way, and no mention of it in the article…

      umm, nevermind, google says:

      http://developer.apple.com/documentation/performance/Conceptual/vImage/Introduction/chapter_1_section_1.html

    • David Allen Parizek Jr 21:19 on February 28, 2009 Permalink | Reply

      This being open source might help someone trying to add image editing features and figure out best way to do it sans IKImageView:

      http://seashore.sourceforge.net/index.php

    • Jonathan 21:20 on February 28, 2009 Permalink | Reply

      Hi David,

      Yeah so my solution is in the works at the moment. As part of Scribbler I’ve written my own image-editing view. As soon as I’m content that it’s working correctly then I’ll open it up.

      vImage is cool.

    • Jonathan 21:23 on February 28, 2009 Permalink | Reply

      The problem with Seashore is the license. GPL is no good to me. When I’m done ESImageView (as it’s called) will be BSD.

    • David Allen Parizek Jr 06:21 on March 1, 2009 Permalink | Reply

      Here we go, sample from apple, it is sweet as far as doing what I need:

      http://developer.apple.com/samplecode/Cropped_Image/index.html#//apple_ref/doc/uid/DTS10000388

    • David Allen Parizek Jr 06:23 on March 1, 2009 Permalink | Reply

      RE: above post, running the sample app is great, not sure how to get the pbx files updated to XCode… XCode does not want to deal with them, but can still look at all the code…

    • David Allen Parizek Jr 06:33 on March 1, 2009 Permalink | Reply

      Ok, it is an old pbx example. To get in to XCode just create new XCode project and add most all of the sample’s files into your new project and it built for me no problem.

    • Jonathan 11:55 on March 1, 2009 Permalink | Reply

      That’s a really interesting example actually, thanks David. I’ve implemented selection and crop in a completely different way, drawing my selection rect over my image with a CGLayer, then using the CGLayer as a mask so I can create a composite CIImage with the area that would be lost in crop processed with a blur filter. I’ll have to have a good look at this and see how well it works for large images.

      Also, all you need to do is change these old .pbproj files to .xcodeproj and xcode will handle them fine :)

      Thanks again, you’ve given me food for thought.

  • Jonathan Dann 22:07 on August 18, 2008 Permalink | Reply  

    Scribbler 

    I’ve been putting off writing about Scribbler for a while because it’s really difficult to put into words properly the ideas I want to convey. I’ve become very attached to my little application since its early inception, not only because of the time I’ve spent, but also for how it has helped me find my creative side. The only reason I haven’t put screenshots up yet is because I’ve been talking with a designer about making the icons, and developing the site and brand of Espresso Served Here (need to catch myself each time I write ’severed’). They guys I’ve got to do that part of the app, which is completely beyond me, are really talented and I’m getting very excited about having all the artwork finished. As soon as it is ready, I’ll start posting screenshots.

    It was writing my MSc thesis that gave me the desire to write a new LaTeX editor. I decided that I needed an app that understands how people work with large documents. Specifically, people work on projects. Each of these projects can contain many source files: LaTeX source itself, images, BibTeX files and PDFs; and Scribbler knows how to handle them all. You organise your project in Scribbler just like you’d organise your iTunes or iPhoto library, while Scribbler takes care of files on your hard drive. You can make new LaTeX and BibTeX files within Scribbler itself, or simply drag-and-drop a file from any other program to add it to your project.

    When writing, having Scribbler keep track of your project soon shows its advantages over keeping each document separate. To include an image in your LaTeX source, just drop it on the text and Scribbler will write the code for you. You no longer need to care where the image file is in the filesystem, why should you?

    Another plus to this approach comes when writing labels and citations. As Scribbler looks after your project it knows all the \label{} keys you’ve written and will suggest them for you when you type \ref{} (for the LaTeX-uninitiated, that makes cross-referencing very, very easy). The same works for all your citations.

    One of the things I always found myself doing when writing in LaTeX was editing my images so I decided to add some image-editing functionality for common tasks. Scribbler allows you to re-colour, crop, rotate and apply filters to your images, so you only need to open up Pixelmator or Photoshop when you really need to.

    I’m really looking forward to showing off Scribbler to you all, and when you use it I’m sure you’ll agree that creating beautiful LaTeX documents can be done in a beautiful environment. After all, one of the reasons we all use LaTeX is because we care about how things look. I’m working really hard on some features that will blow you away, and I’ll keep-on making it better.

     
    • Mark Aufflick 13:00 on August 20, 2008 Permalink | Reply

      Hi Jonathan, Just heard you on the Mac Developer Roundtable. I’ve just made the switch from emacs for my latex/bibtex work to BibDesk and TexShop.

      I love BibDesk (and have already submitted one patch), but TexShop (and iTexMac2) are kind of lame so I’m very interested in a beautiful (lickable even?) latex application for the mac.

    • Zettt 23:21 on August 22, 2008 Permalink | Reply

      I heard your talk on the developer roundtable, too. Hope that this will be an amazing LaTeX app.
      Best would be to subscribe to your blog to get any news, or not?

    • Jonathan Dann 23:29 on August 22, 2008 Permalink | Reply

      Hi Mark and Zettt,

      Thanks for the comments and the kind words! I’ll be posting more as soon as I can so yeah subscribe to my RSS feed and you’ll be updated as soon as I post more information.

      Lickable? It’s gorgeous :)

    • Zettt 22:21 on August 23, 2008 Permalink | Reply

      I just subscribed. Hopefully there will be a testable beta out soon. How much will it cost?

    • Jonathan Dann 00:37 on August 24, 2008 Permalink | Reply

      Hi Zettt,

      I’m hoping to have a beta ready for testing in October, if there are issues I haven’t come across then the version 1.0 will be a little after that.

      As for price I’m not going to say anything at the moment. I’ll put more details up in the near future, though.

    • Zettt 07:12 on August 24, 2008 Permalink | Reply

      OK, thanks for the information. I’m waiting. ;)

    • Andrew Robinson 10:48 on September 19, 2008 Permalink | Reply

      Hey Jonathan, we met yesterday at IPEM – thought I’d check out your site! Keep my updated on the Scribbler app, sounds like a good piece of kit!

      Andy

    • Jonathan Dann 10:51 on September 19, 2008 Permalink | Reply

      Hi Andy,

      Glad you remembered the name of the site! My Nuclear Med went fine but then the Radiotherapy was awful, he kept interrupting me and then I lost my nerve! How’d your ones go in the end?

    • Andrew Robinson 22:54 on September 19, 2008 Permalink | Reply

      I love coffee so it wasn’t difficult!

      Who was your examiner? My RT went OK but wasn’t amazing, DR went a bit better though, just waiting in the results now…

    • Mark Aufflick 02:46 on October 16, 2008 Permalink | Reply

      How’s that beta coming along? I’m writing some papers at the moment…

      By the way – are you looking at any integration with BibDesk?

    • Jonathan Dann 20:25 on October 16, 2008 Permalink | Reply

      Hi Mark,

      It’s coming along… but a little slower than I hoped. I’ve finished at the NHS now, but that took up all my time the last couple of months, so I’ve fallen way behind my schedule. As soon as I can I’m going to get it out in beta. I just don’t want to release a product where I know that some areas need more attention :) I hope that when it’s done, you’ll like what I’ve come up with.

      As for BibDesk, version 1.0 won’t have integration with it as far as I can see. One of my reasons for this is being able to give the user a seamless experience when it comes to writing a LaTeX document. However, even when Scribbler is managing the bib files in the project, it’s real simple to just edit the bibliography part in BibDesk if the BibTeX editor in Scribbler doesn’t do all you need it to. As the product evolves I’ll add more integration where it’s necessary, though.

      Really glad you’re staying interested with my progress, I’m working as hard as I can now. I want to be able to open up my company with a real bang. :)

    • PC 07:10 on April 23, 2009 Permalink | Reply

      Hi Jonathan, long time no see!

      So what is happening with Scribbler? Did you quite its development or things are just going slow?

      Cheers

    • jay 13:46 on September 7, 2009 Permalink | Reply

      Hey,

      I am wondering what’s going on with scribbler too. It should be a very interesting app.

      Regards, J~

  • Jonathan Dann 19:26 on July 4, 2008 Permalink | Reply  

    NSViewController Late Night Cocoa Episode 

    You may have seen the articles Cathy Shive and I wrote on her blog about NSViewController and how to get it working well in your apps. Well now you can hear us talk about it! Scotty of the Mac Developer Network kindly had us both on for an episode of Late Night Cocoa, which you can download from iTunes here. It’ll be on the MacDevNet site too.

    I talked about my preferences window system, I’ll have to wait a few weeks until I can put it up here with a sample app. Work will let up in August and I’ll have time to iron out a few bugs.

    Thanks again to Scotty for having me on the episode, it was awesome!

     
    • Rob... 21:25 on July 7, 2008 Permalink | Reply

      Excellent episode. As I’m just getting back into native app writing after a long time on the web, this came an at apt time for me to hear about the future.

      Rob…

    • Jonathan Dann 21:35 on July 7, 2008 Permalink | Reply

      Glad you liked it, and it was useful. Really hard to talk about programming like that, hard to avoid just talking in code! ;)

    • Daniel Farrell 01:12 on July 17, 2008 Permalink | Reply

      Hi Jonathan,

      Yeah good job, enjoyed listening. I did have one question; what is the difference between a stack and a tree design?

      Anyway, the real reason I’m here… I would love to be a beta tester for your LaTeX management application! I’m writing a thesis at the moment (in physics too actually).

      Cheers,

      Dan.

  • Jonathan Dann 11:11 on June 27, 2008 Permalink | Reply  

    Site redesign imminent 

    I’m slowly getting everything ready to get espresso served here off the ground.  Part of that has been talking to a couple of graphic designers about site design and icon design.  After all, I’m a programmer and I can’t draw for the life of me.  Photoshop to me is an app that just puts loads of stuff all over my system.

    Hopefully sometime in August things will start to take shape.

     
  • Jonathan Dann 10:02 on June 18, 2008 Permalink | Reply  

    Creating iTunes Scrollers 

    Apple introduced new scrollers in iTunes 7 and then moved on to give us the HUD, which many developers want their own scrollers for too. In Leopard, many of us thought that these would come in a nice, shiny box; but as they didn’t we’re all forced to roll our own. The common method is to draw all the components in Photoshop and then make a composite image when subclassing, but now with NSGradient and some nice additions to NSBezierPath all these have become quite easy to do, even for those with little artistic ability.
    (More …)

     
    • Jonathan Watmough 18:14 on September 4, 2008 Permalink | Reply

      Hi Jonathan,
      Interesting post and project. It was funny to run it, because it looks like it’s out by a pixel, but then, looking at iTunes, that now looks wrong to me. Thanks for ruining iTunes for me!
      Anyway, Scribbler sounds like a great idea. I use a really cut down selection of styles to give my documents some regularity in OpenOffice. When using other peoples documents in Word, the generally messed up styles drives me nuts. Will Scribbler have a set of predefined document templates? It seems like the nice TeX output would be great for people who like documents to be formatted in a standard way.
      How are you planning to ensure that a TeX stack exists and is valid on the destination Mac?
      Good luck with being an ISV!

    • Jonathan Dann 20:34 on September 5, 2008 Permalink | Reply

      Hi Jonathan,

      I *think* I know what you mean about it being out by a pixel, is it on the right-hand side of the vertical scroller? If it is then you see it in apps that don’t have a 1-pixel border around their scroll views, so the edge of the scroller is exactly aligned with the edge of the window. You can then see the effect this has when you move the window so it is directly above the desktop after being above another application’s window (if that makes sense). If that’s not what you mean then et me know and I’ll take a look.

      As for Scribbler, I’m working on a set of project templates and document templates to help get people started, but these will grow as they’re added by users. If you have your own, you won’t have to send it to me for inclusion in the app either, just put it in the Scribbler directory in ~/Library/Application Support and Scribbler will find it. If you have any really good templates then please send them along!

      As for the TeX stack, the only part that doesn’t work if there’s no valid distro on the system is typesetting itself. For those that download the app without TeX installed, I’m going to point them to the most current MacTeX distro for simplicity, possibly downloading it and mounting the disk image it for them so all they have to do is install. If I’ve misunderstood your question then please forgive me!

    • Jonathan 03:15 on February 24, 2009 Permalink | Reply

      This is awesome. I especially appreciate the fact that you aren’t using images. I’ve been taking a lot of extra time to draw out my own interfaces lately. I just have one problem now. I realized it after I threw your scroll bars in. My contained table view still has Aqua headers. Know of any other Classes for taking care of that or do i need to get to work? Also I’m planning on modifying this to also accommodate the black/grey scroll bars that you also see in iTunes in addition to others like iMovie. Want me to send you my changes when/if I finish it?

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

      Hi Jonathan,

      Glad you like them, I’m afraid looking back on them now they need a bit of a redesign! I’ve just been thinking of using black ones myself so, yeah, send them along when you’re happy with them. I’d appreciate it.

      As for the headers, you just have to subclass NSTableHeaderCell. You’ll have to draw both the background and the text but read the 10.5 AppKit release notes, they give you good pointers on using NSBackgroundStyle in cells for the embossed text look.

      I’ll see what I dig up in my own source though as I’m sure I did this a while back.

    • Jonathan Badeen 17:59 on February 26, 2009 Permalink | Reply

      Thanks Jonathan,
      I’ve been working on finessing your scroll bars in addition to adding the dark styled ones. There are still at least a couple things i need to do/improve still. You can see them implemented in my app here – http://www.badeen.com/fastcapture1.png. When I’m done I’ll send it your way. I did have one question though. Do you know how I can go about drawing my own resize corner or just get it to display transparently in the lower right. I essentially would like to get it to look more like it does when there is no scroll bar – http://www.badeen.com/fastcapture2.png

    • Jonathan Badeen 04:56 on February 28, 2009 Permalink | Reply

    • Gustavo 09:09 on September 6, 2009 Permalink | Reply

      Jonathan hello.

      Thanks great example, I wish I understand it better, im a novice ( with some experience already) and I thought this goal wold be easier than I say in the example.. Im trying to implement something similar in my application, but definitely you have gave me a good idea on how to achieve it.

      Must read again to understand the whole of what you are doing in the example… :S

      G.

    • Michael 06:58 on October 6, 2009 Permalink | Reply

      Hey Jonathan.

      Thanks for the amazing NSScrollerView subclass. I’m currently working on a project made to look similar to the itunes store. http://www.publictunes.com/screenshots/pic1.png the vertical scroller works like a charm. Unfortunately the horizontal one appears to have some fundamental drawing errors. I have looked at the code and had trouble grasping how the curves were made that cut into the increment decrement arrows for the slider tracks. If you could please explain this or fix the class it would be much appreciated.

      -Mike

      • Michael 07:00 on October 6, 2009 Permalink | Reply

        Oh i forgot. Here is a blow up of the horizontal slider problems. http://www.publictunes.com/screenshots/pic2.png

        • bryscomat 16:48 on October 26, 2009 Permalink

          I was able to fix this by putting this in the arrowsSetting: method:

          ESScrollerArrowsSetting setting;
          if (self.isVertical) {
          if (NSMaxY([self rectForPart:NSScrollerDecrementLine]) == NSMinY([self rectForPart:NSScrollerIncrementLine]))
          setting = ESScrollerArrowsTogether;
          else
          setting = ESScrollerArrowsApart;
          }
          else {
          if (NSMaxX([self rectForPart:NSScrollerDecrementLine]) == NSMinX([self rectForPart:NSScrollerIncrementLine]))
          setting = ESScrollerArrowsTogether;
          else
          setting = ESScrollerArrowsApart;
          }

          return setting;

    • Philipp 13:47 on October 25, 2009 Permalink | Reply

      Hi Jonathan,

      Thank you for the great post and the code.

      What license is the code released under? I looked through the project folder but couldn’t find any.

      Thank you in advance!

      • Jonathan Dann 12:39 on October 31, 2009 Permalink | Reply

        Oh yeah, consider it BSD If you want to credit me feel free, but you don’t have to.

        I need to clean up that code. It’s embarrassingly bad now!

    • Jonathan Dann 12:40 on October 31, 2009 Permalink

      Sorry about that. I must have got horribly distracted. I’ll update the code soon! It need s a good looking-at.

  • Jonathan Dann 13:54 on June 15, 2008 Permalink | Reply  

    New Site Address www.espresso-served-here.com 

    Hi all,

    Just thought I’d let you know that you can now reach this website at http://www.espresso-served-here.com. I’m going to slowly be making changes to the site as I develop the brand for my software company. All previous links around the web to http://jonathandann.wordpress.com will still work though.

    Jon

     
  • Jonathan Dann 21:00 on May 13, 2008 Permalink | Reply  

    NSTreeController and Core Data, Sorted. 

    Having recently taken the plunge into Core Data I decided it was time to rip out all the model code from my current application and replace it with a Core Data version. After about a day I had my app up and running again but with one huge problem, the content of my NSOutlineView always appeared in a random order. Such is the problem with Core Data that NSManagedObjects store their to-many relationships in an NSSet, not an NSArray, which is unordered. So when your NSTreeController tries to display its data it appears in a random order.

    This is not nice, imagine if the playlists in your iTunes library always changed their order? It gets even worse if your user wants to use drag and drop. In this case they decide the order, and they’d probably want it to stay that way.
    (More …)

     
    • mark 11:40 on May 26, 2008 Permalink | Reply

      Nice and clearly written article! I have a suggestion though. Now that the sortIndex has become persistent and a part of the model, according to MVC, it means that sortIndex is now owned by the model. It should be the model who keeps the node indexes under control or says which nodes accept new/moved children.

      It bothers me that in this implementation the business logic is embedded in the controller. If you modify the model’s sort indexes programmatically, your tree controller does not get to know about it. Also, children can be added or moved programatically and the model can become inconsistent because the decision whether the move is accepted, is made by the NSOutlineView delegate (!) – not the model.

    • Jonathan Dann 12:37 on May 26, 2008 Permalink | Reply

      Thanks for the compliment Mark!

      I’m afraid I have to disagree with you on this though ;-) but I’d really like for you to explain how your implementation would work though, I’m sure there’s places I can improve this.

      Your first point is correct, the sort index is owned by the model, this is essential for the persistence of the sort order. I can’t think of another way of keeping the sorted order between sessions. When doing this, I don’t see why my model objects should have any knowledge of each other at all, that’s why the code to keep the sort indexes in check is in the controller. The controller knows about all of the nodes and the NSTreeNodes. Making the model objects (too) aware of their surroundings seems to break encapsulation, they should just be happy to exist in their own universe (in the larger sense, obviously they can traverse their parent and children relationships).

      Your point about the models saying which nodes accept or deny new nodes is fair, but I think the logic that governs which nodes accept new/moved children is forced to be in the NSOutlineView’s delegate due to the way the view works. When you log the proposedIndex when -outlineView:validateDrop….. the outline view returns -1 is the cursor is over a child or at the root of the tree. There’s clearly no way to obtain the NSManagedObject/NSTreeNode that has an indexPath with a -1, so the allowing or denying moves to the delegate.

      In my application I have a source list that looks like iTunes but has groups and files like in Xcode. I may have been influenced by my own design requirements, but dropping dragged items onto a file makes no sense and one clearly shouldn’t be able to drop a group on to one of its subgroups. The latter could be done in the model code by would still be mediated in the outline view’s delegate. It would ask the model to validate the drop of the proposed item and the model would have to see if the proposed item was somewhere along the parent relationships. The reason I didn’t do this was for flexibility, by using the model you’re not locking developers in to a certain mindset, there are cases when you would want to be able to rearrange the tree arbitrarily and this model accommodates that.

      You’re correct that modifying the sort indexes programmatically doesn’t inform the tree controller, but the resort would take place when NSTreeController’s -rearrangeObjects is called. I would think it quite difficult the modify them programmatically in the first place without messing up the tree. You’d have to decrement and increment the other children around it and, although you can get to the other children using the @”parent.children” keypath, even doing this seems to me to break encapsulation.

      You are correct that it could be done, and the model could take care of it all but there would be an immense amount of code to write and some edge cases that may be hard to find. The other confounding factor is that time = money, and getting the tree controller to steal the NSTreeNodes -lastIndex which it has just magically placed in the right place is just too easy to pass up. Although I’m still not convinced that I’ve put things in the wrong place.

      The outline view’s delegate methods don’t just allow/deny drops they also return values that the view itself uses to communicate the actions to the user. These methods tell the view to draw its little blue boxes and lines to show where (or not) the drag can go. As the sorting of the tree (in the case of my app) is really a view-related process (its just the ordering the user wants to see) then I think its OK to allow/deny in the delegate, one of the reasons delegates exist is so you can have a third party (almost) that you can ask, “what do you think?”.

      Finally, the case of adding and moving children programatically should be done using the tree controller, its whole point is to manage the tree, so if you use ESTreeController’s methods to add/move/remove then you’ll never end up with an inconsistent tree. I think it fit perfectly that the tree controller updates the model’s sort indexes as the controller then handles keeping the tree consistent. Doing otherwise, I say hesitantly, would be programmer error.

      I hope this hasn’t been to rambling, I’d love to hear of your opinions on this. Thanks again for reading the post and commenting, you’ve made me think again about my design decisions, hope you don’t mind that I’ve disagreed.

    • mark 11:49 on May 27, 2008 Permalink | Reply

      Thanks for your reply, Jonathan. I think that disagreement is a good basis for a discussion. Also, I’m glad you’re representing files in the tree of your app, because it’s a pretty good example of what I was trying to say. :-)

      With the filesystem, we have a case of model updating “programatically”. Files maybe added or removed behind our apps back. Our model might observe these changes via FSEvents and update itself. (The tree controller is notified via KVO.) Or we could have a separate model controller feeding the model, anyway it’s not a tree controllers job to do.

      Model is already a tree by itself, and file items “know” about each other via parent/children relationships. Whether the file can be renamed/moved or not, is totally dependent of the file system – it’s not a decision of NSOutlineView delegate or NSTreeController to do. Also, moving a file might actually fail even after it looked like it would succeed. Again, only the model will know.

      To make things harder, in addition to the content observed to be present in the actual file system, the model may contain other content which is only stored in the core data database. We might retain some data for the files that once deleted might reappear later, or like XCode, we could have “groups” or “smart groups” that only live in our model. So to sum up, we have many kinds of mirroring and syncing going on and we haven’t even introduced the UI yet.

      NSTreeController is there to represent the model for the view layer, not to be the model.

      Then things start to get more complicated. The possibility of having multiple views to the same model data requires introducing another model layer.
      But what do you think about this so far?

    • Jonathan Dann 18:12 on May 28, 2008 Permalink | Reply

      I think I may know why I’m disagreeing with you (now only on some points ;) ).

      You say that the model should update itself and the tree controller shouldn’t be the one doing it. The way I was thinking is that the tree controller should take care of updating the model because it is a controller, now you bring forth the idea of a model controller that does the model updating and the tree controller can then be left to handle tree stuff.

      This is how I’ve done it really in my app as I have an NSDocument subclass that is my model controller. So I have a (relatively) dumb model and a controller that does the decision-making, this I think is very much in-keeping with MVC.

      You’re correct that the tree controller is there to represent the model for the view layer, hence why I’ve made it the one to update the sort indexes of the model after the drop acceptance (wherever that may finally be) as it knows all about the user’s requested ordering.

      So do you think then that in the outline view’s delegate methods, the delegate should ask the model (or model controller) if the drop should occur?

      In my app, I don’t quite represent the file system as you’re thinking of it. It’s like (the default setup of) Xcode, where all the files are reference arbitrarily and the “groups” aren’t represented on disk, hence why I’ve allowed arbitrary re-ordering of nodes and groups that doesn’t affect the file-system itself. I’m going to be using FSEvents in the future but at the moment I’ve implemented new file creation and drag and drop onto the tree from the Finder that then moves the files or simply creates a node that references the files’ current locations.

      It’s this alternative view of the file-system that has caused me to allow the outline view’s delegate to allow/deny the drops. Interestingly though I have setup my model to control whether a node can be dragged in the first place, and other things like whether a particular node is a “group node” (see -outlineView:isGroupItem: ) or can collapse/expand. This resulted from trying to determine if the group was to be in the uppercase text like in iTunes. Originally checking the name of the group node was fine, but then I realised if a user called one of their own group “IMAGES” then it would return YES in -outlineView:isGroupItem: so the most reliable way is to give the group entity an isSpecialGroup attribute.

    • Alej 19:51 on June 5, 2008 Permalink | Reply

      The file hosting is not working anyone mind re-uploading it on another server (Eg. Rapidshare) thanks

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

      Yeah really sorry about that, an it seems that although I’ve paid $20.00 for 5GB of space on WordPress I still can’t upload the zip file for the project. hmmm….. I’ll fix this. Bear with me.

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

      here it is, I’ll update the post.
      http://rapidshare.com/files/120385221/SortedTree.zip.html

    • Moritz 12:51 on June 7, 2008 Permalink | Reply

      Hi,
      I’m using xCode 3.0 on PPC G4 Leopard 10.5.3.
      When I open the SortedTree xcode project I get an error message, that the project was created with a newer version of xcode than mine. Thus I cannot compile and run the project. I’ve heard rumors that there is a xcode 3.1 in beta out there. Can anyone advice as to how to solve this issue.
      Thank you very much.
      Also Jonathan thank you very much for this tutorial. It is very valuable even just the source.
      Greetings
      Moritz

    • Jonathan Dann 13:10 on June 7, 2008 Permalink | Reply

      yeah sorry about that I’m running the iPhone SDK which has 3.1 so many improvemts it’s worth getting

    • Moritz 16:16 on June 8, 2008 Permalink | Reply

      Hi Jonathan,
      Could you send me a working executable of SortedTree? I would really like to execute your programm because I don’t fully undestand the code yet. I think that would really help me to figure out what your code really does.
      Thanks and greetings
      Moritz

    • Daniel 21:57 on June 17, 2008 Permalink | Reply

      Hi I can’t get your code to compile because you built it with XCode 3.1 which is not yet available for us non-iPhone plebs.

      Can you please release a version for 3.0?

    • ian 22:47 on June 17, 2008 Permalink | Reply

      hey jonathan, i can’t thank you enough for this. i’m a hack working on my first cocoa app and i’ve been banging my head against this for weeks it seems.

    • Jonathan Dann 10:26 on June 18, 2008 Permalink | Reply

      @Daniel,

      I will when I get a sec! Before then you can create a new project with the same name and drag in all the files (including the NIB and info.plist) into Xcode after deleting the originals. That should do it.

      @ian

      You’re very welcome, people were so helpful when I started it’s good to be in the position to offer advice

    • Daniel 21:23 on June 18, 2008 Permalink | Reply

      “Before then you can create a new project with the same name and drag in all the files (including the NIB and info.plist) into Xcode after deleting the originals. That should do it.”

      Tried that. No luck. It just hangs. I there’s an incompatibility with the version of IB you’re using and the official version 3.0. You used an unreleased beta so I expect there are some differences in the format of the XIB.

    • Issi 23:27 on June 21, 2008 Permalink | Reply

      Thanks for the post. Very informative and thought provoking.

    • MDK 16:31 on June 26, 2008 Permalink | Reply

      Hey Jonathan,

      Would like to bug you again, not 100% related but since you mentioned NSPersistentDocument and core data in other posts…

      I’ve got a core-data based app which is a non-document-based app (think iTunes, etc) yet I’d like to use the document-based machinery for it. In other words, I want to use NSPersistentDocument (ie. it’s undo features) + XSControllers architecture except that I’d like the document to be hard-wired to only file on disk — with no support for opening file types etc.

      Is there some smart way to do this without changing Info.plist? Would you recommend this approach? Perhaps instantiating the Document myself and calling/simulating the makeWindowControllers machinery myself?

    • Jonathan Dann 23:52 on June 26, 2008 Permalink | Reply

      Do you want to have an app that has many windows that can show the user their data but in different ways? Like the way iTunes does when you double-click a playlist? Can you be more specific with what you’re trying to achieve in terms of using the program?

    • Rob 21:18 on October 1, 2008 Permalink | Reply

      Jonathan,

      Nice example app very helpful, was wondering if you where able to do the same thing but with undo working it, right now if you drag and drop a leaf from one place to another and hit undo the results from the undo are very random.

      Rob

    • Jonathan Dann 17:50 on October 3, 2008 Permalink | Reply

      Hi Rob,

      Sorry it’s taken me a while to get back to you on this, I’m in a rather internet-less state at the moment!

      Unfortunately I haven’t been able to get undo working for it, and you’re right that its goes haywire! I think it would require some inventive creation of undo-groupings if it’s possible to fit it into the undo architecture of Core Data at all.

      In Scribbler, I’ve actually turned of the Core Data undo management and written my own for that parts that make sense. The way I use this source list, for management of files and folders like iPhoto does, it’s not a requirement to make moving a node an undoable action, so I haven’t pursued it.

      Sorry I can’t shed any more light on it, if you get somewhere with it, please let me know.

      Jon

    • Bill Monk 20:57 on October 6, 2008 Permalink | Reply

      Nice article.

      Small bug in NSTreeController_Extensions.m: the -flattenedContent method throws a valueForUndefinedKey exception, “the entity Group is not key value coding-compliant for the key descendants.”

      The trouble is a mere copy/paste error, -flattenedNodes and -flattenedContent are very similar, and both do

      [node valueForKey:@"descendants"].

      However -flattenedContent is working with ESTreeNodes, not NSTreeNodes. The descendants key works in -flattenedNodes (calling your -descendants category on NSTreeNode), but clearly @”children” was intended here, to call ESTreeNode’s -children method.

      That is, in -flattenedContent :

      if (![[realNode valueForKey:[self leafKeyPath]] boolValue])
      [mutableArray addObjectsFromArray:[realNode valueForKey:@"children"]]; // < -

      This works fine.

      -Bill

      PS, dragging leaf objects and undoing seems to be working here – no problems.

    • Bill Monk 20:36 on October 8, 2008 Permalink | Reply

      Actually a better fix is to add a -descendants method to ESTreeNode, analogous to the category on NSTreeNode. Using children as described above is not quite right; won’t fully enumerate complex trees.

      In ESTreeNode.m:

      - (NSArray *)descendants;
      {
      NSMutableArray *array = [NSMutableArray array];
      for (ESTreeNode *child in self.children) {
      [array addObject:child];
      if (![[child isLeaf] boolValue])
      [array addObjectsFromArray:[child descendants]];
      }
      return [[array copy] autorelease];
      }

    • Jonathan Dann 21:26 on October 16, 2008 Permalink | Reply

      Hi Bill,

      Sorry for the late reply and for not updating that project properly. I should be moving over to my own server soon where I’ll be able to host whatever files I want to!

      I realise now that I’d changed my implementation of -[NSTreeController flattenedContent] ages ago to be:

      - (NSArray *)flattenedContent;
      {
      return [[self flattenedNodes] valueForKey:@”representedObject”];
      }

      which really helps in cutting down on such copy/paste errors. You’re correct that to be correct in your implementation that ESTreeNode needs a -descendants key. As I’d written the above method I hadn’t run into this one.

      I need to have another look at undo, I’m surprised that its works for you. Maybe it was fixed be an OS update?…

      Just tried it an it tells me that the undo manager is left in an invalid state with too many nested undo groups. Can you try and reproduce this on your machine?

      Jon

    • Bill Monk 06:08 on November 6, 2008 Permalink | Reply

      Hi Jon,

      Thanks for fix above. I believe I finally figured out a way to make undo work reliably. The basic problem is that while Core Data correctly does the undo and redo on the data, NSOutlineView doesn’t necessarily see the changes and as the display gets out of the sync with the model, eventually an undo/redo command is issued without the correct data on the stack to perform it, resulting in the “NSUndoManager is in invalid state, undo was called with too many nested undo groups” error.

      In my my, the user must be able to sort the outline by various criteria, as well as sorting just a few selected rows within any given group. This has to be un-doable, and however the outline happens to be sorted/subsorted when it’s saved, it should re-open with the items in the same order. Obviously, that’s where I’m using your sortIndex idea. I set the sort descriptors as needed, update the sortIndices with your -updateSortOrderOfModelObjects method. When the document is re-opened, the sort descriptors are first set to one that sorts by the sortIndex, recreating the previous order. Undo works on everything, sorts, name edits, drag reordering, collapse/expand.

      To get undo working in your sample project, I downloaded a fresh copy and made these changes. It may not be the best way, but it does seems to work.

      1) In ESTreeController.m, change -reloadData so that it expands -and- collapses items according to the reloaded data. This is to prevent the problem of collapsing a group item, undo it and having work, then redoing and having the Edit menu flash correctly (indicating there was something on the stack that gets redone) but there being no visible change.

      Something like:

      - (void)reloadData;
      {
      [super reloadData];
      NSUInteger row;
      for (row = 0 ; row < [self numberOfRows] ; row++) {
      NSTreeNode *item = [self itemAtRow:row];
      if (![item isLeaf]) {
      if ([[[item representedObject] valueForKey:@”isExpanded”] boolValue])
      [self expandItem:item];
      else
      [self collapseItem:item];
      }
      }
      }

      =====
      2) When Core Data undoes/re-does something, say a sort, the model data is changed correctly, and all the sortIndex values are restored, the outline view needs to be told to reload its data. NSUndoManagerDidUndoChangeNotification/NSUndoManagerDidRedoChangeNotification work for this. If you allow sorting on criteria other than sortIndex, as described above, Core Data may have changed a bunch of sortIndex values, and to get the un-done sort displayed, we need to tell the treeController to to re-sort by the sortIndex before anything happens to call -updateSortOrderOfModelObjects, which will overwrite all the un- or re-done sortIndexes with the item’s current, pre-undone position in the outline. If that makes sense…

      - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
      {

      [[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(undoManagerDidUndo:)
      name:NSUndoManagerDidUndoChangeNotification
      object:nil];
      [[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(undoManagerDidRedo:)
      name:NSUndoManagerDidRedoChangeNotification
      object:nil];
      }

      - (void)managedObjectsDidChange:(NSNotification *)notification
      {
      [outlineView reloadData];
      [treeController setSortDescriptors:nil];
      [treeController setSortDescriptors:[self treeNodeSortDescriptors]]; // keep treeController sorted by sortIndex (except during a sort by name, etc)
      }

      - (void)undoManagerDidUndo:(NSNotification *)notification
      {
      [outlineView reloadData];
      [treeController setSortDescriptors:nil];
      [treeController setSortDescriptors:[self treeNodeSortDescriptors]]; // keep treeController sorted by sortIndex (except during a sort by name, etc)
      }

      ====
      3) I was constantly getting this error

      SortedTree Error setting value for key path treeNodeSortDescriptors of object (from bound object [entity: TreeNode](null)): [ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key treeNodeSortDescriptors.

      To stop it, I just turned off the NSTreeController sortDescriptors binding to ESAppDelegate’s treeNodeSortDescriptors, and instead, set the sort descriptors in code in -awakeFromNib or -windowControllerDidLoadNib.

      ====
      4) These errors were constantly being logged:

      -[ESTreeNode isSpecialGroup]: unrecognized selector sent to instance 0×193e00

      -[ESLeafNode isSpecialGroup]: unrecognized selector sent to instance 0×1a4910

      [ valueForUndefinedKey:]: the entity Leaf is not key value coding-compliant for the key isExpanded.

      -[ESLeafNode canExpand]: unrecognized selector sent to instance 0×171f80

      According to http://www.cocoabuilder.com/archive/message/cocoa/2008/6/9/209710

      these seem to be due to a bug in Leopard which causes the -outlineView: isGroupItem: delegate method to sometimes be sent a dealloced object when Undo/Cmd-Z is used immediately after a creating a new object with CoreData when the NSOutlineView is set to SourceList style in InterfaceBuilder.

      Whew… My fix was just to turn off the source list style. In my app I don’t use the style, so changed -outlineView: isGroupItem: to always return NO.

      Well, sorry for the long post, but I wanted to report back since your code helped me get my CoreData/NSTreeController app off the ground. Shoot me an email at the address in this form if you’d like the edited project. Thanks again.

    • StrAbZ 00:09 on August 14, 2009 Permalink | Reply

      Great post, I learned a lot of things, and I’m starting to find my way in all those bindings, core data, and other cocoa stuff ^^

      But I have a question… how can I use this model, and now which is the select node type? (ie: Group or Leaf).

      I mean in my case, the Leaf node, contains a list of object, that i want to display in a table view… but the Group object doesn’t… and you guess, when I try to bind the content set to the tree controller selection, it doesn’t work since Group doesn’t not conform to the specified accessor…

      Do you have any clue about that?

      I also noticed that i can’t have the root object unselected… when the app starts, even with the isSelectable attribute to NO, it is still selected…

      Thank you very much !

    • Hank McShane 12:58 on September 12, 2009 Permalink | Reply

      Thank you so much for this. I was struggling with understanding a tree controller and core data… but your example makes it all clear to me. You have a tree node, and the node can be either a group or a leaf… very simple. The rest of it with the expanded state saving and the sorting is invaluable… many many thanks.

    • Jonathan 13:50 on August 23, 2009 Permalink | Reply

      @StrAbz Glad it was of some help to you. When it comes to heterogeneous nodes in the tree, selection isn’t best done exclusively with bindings. You’re better using the NSTableView delegate methods to find out when the selection changes and update the content of your table view’s array controller manually based on the newly selected object. So your table view is still bound to the array controller, but the array controller’s content is managed in code by you.

      The tree controller and outline view both have flags set in IB concerning selection, try experimenting with the “selects inserted objects” and “avoids empty selection”.

    • Brad Gibbs 21:34 on October 24, 2009 Permalink

      Hi Jonathan,

      First off, thanks for posting all of the information you’ve posted. I’m using ESScrollView, the animating outline view code and, I’m trying to use the code described in this post. I’ve learned a lot from working through your code.

      I’m having issues trying to extend the code in this post. I can see two possible ways of doing so — 1. make new model objects that are subclasses of ESGroupNode and ESLeafNode, or, 2. add a relationship to ESGroupNode and ESLeafNode for the represented Object. I’ve implemented the first approach, but, it gets a little messy and it means that these model objects can’t inherit from any other classes. The second method seems to mirror a non-Core Data NSTreeNode application more closely (ie., DragAndDropOutlineView sample code from Apple), and, it frees the objects up to be subclasses of other objects. It also seems a bit cleaner, especially given that these objects are going to be used in many different contexts in my app.

      I haven’t tried the second method, yet. I was wondering which path you’ve chosen.

      Thanks.

      Brad

    • Jonathan Dann 12:36 on October 31, 2009 Permalink

      It’s an interesting question, and there’s no correct answer. I starting with the former and then moved over to the latter. I ended up with each node having a “representedObject” relationship to other entities.

      It may be a little harder to manage, but then I also write some classes that I go through when mutating the model, so I ensure, using these “model controllers” that I’m not just changing my model ad-hoc and it keeps it in an correct state.

      Also remember that your class hierarchy can diverge from your entity hierarchy when using Core Data.

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
esc
cancel