Newbie question: NSDocument and standard file formats

  • Hello,

    I'm trying to write my first Cocoa document-based application, which
    will manipulate PNG images.  At this stage, I'm trying to write an
    NSDocument subclass which will load data from a PNG image file and
    create an instance of NSImage, which will then be handed to a View
    class for display.

    However, the existing "plumbing" provided by AppKit seems to assume
    that you are trying to read in documents which you have previously
    written out (i.e. proprietary formats which you have invented just for
    your application).  What I want to do is read an existing file format:

    - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:
    (NSError **)outError
    {
    NSImage *img = [[NSImage alloc] initWithData:data];
    [imageView setImage:img];
    [img release];

          return YES;
    }

    However, that naive code does not work, because the data contained in
    the NSData does not appear to be in a format that NSImage understands.

    So I went digging in:

    ADC Home > Reference Library > Guides > Cocoa > Design Guidelines >
    Document-Based Applications Overview > The Roles of Key Objects in
    Document-Based Applications

    And read the following:

    "When the user chooses Open from the File menu,
    theNSDocumentController object displays the Open panel, gets the
    user’s selection, finds the NSDocument subclass for the file (based on
    its document type information), allocates an instance of this class,
    and initializes the object and loads document data by invoking the
    NSDocument method initWithContentsOfURL:ofType:error:"

    So my question is, without subclassing NSDocumentController (which I
    understand you are not supposed to do), how do I tell
    NSDocumentController to simply hand me back an instance of NSImage?
    Or is it even already doing this for me and all I have to do is get
    the class out of NSData?  If so how?

    Thank you for your patience with this basic question.  At the moment I
    feel like I'm falling down the gap between finishing a book on Cocoa
    (Hillegass book in my case) and being confident/competent enough to
    make progress on a genuinely useful application.

    best regards,

    Andrew
  • On 2007 Nov, 25, at 7:25, Andrew Ebling wrote:

    > So my question is, without subclassing NSDocumentController (which I
    > understand you are not supposed to do), how do I tell
    > NSDocumentController to simply hand me back an instance of NSImage?
    > Or is it even already doing this for me and all I have to do is get
    > the class out of NSData?  If so how?

    Well, first of all, you can subclass NSDocumentController if you have
    a need to.

    Regarding the Cocoa Document Architecture (which as far as API is
    concerned means NSDocument and NSDocumentController), it handles
    things like open and save dialogs, associating them with windows,
    splicing them into the responder chain, document change counts for
    undo/redo, all the "generic" document stuff.

    But the Cocoa Document Architecture does not do stuff with the data in
    the file.

    So, now that you understand that the problem is probably not with your
    usage of the Cocoa Document Architecture, that brings us to your code...

    > NSImage *img = [[NSImage alloc] initWithData:data];
    > [imageView setImage:img];
    > [img release];

    I've never done that, but it looks like it should work.  To
    troubleshoot, start by doing an NSLog on 'data' to see if it is what's
    in your file.  Then NSLog the description of img, then imageView,
    etc., etc.
  • On 25.11.2007, at 18:42, Andrew Ebling wrote:

    > What I want to do is read an existing file format:
    >
    > - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:
    > (NSError **)outError
    > {
    > NSImage *img = [[NSImage alloc] initWithData:data];
    > [imageView setImage:img];
    > [img release];
    >
    > return YES;
    > }
    >
    > However, that naive code does not work, because the data contained in
    > the NSData does not appear to be in a format that NSImage understands.
    >

    As jerry already wrote:
    > But the Cocoa Document Architecture does not do stuff with the data in
    > the file.
    >

    It doesn' work because you send the 'img' too early to the
    'imageView'. The new
    document is still in the loading phase and therefore 'imageView' is
    empty.
    Insert NSLog( @"imageView is %p", imageView ); and you get ' ....
    0x0'. In this
    method -readFromData:ofType:error: you may check the data for
    correctness and
    (if valid) assign the data to an instance variable: imgData = [data
    retain];
    (do not forget to release imgData in -dealloc:)

    In the NSDocument method -windowControllerDidLoadNib: you can use
    imgData, create
    an NSImage and send it to the imageView, because it is now loaded, as
    the method says.

    Heinrich

    --
    Heinrich Giesen
    <giesenH...>
  • Hi Jerry,

    Thanks for your help - I've managed to figure this out as a result.

    As you suggested, the code I posted was actually fine.  The problem
    was that the view had not been initialised by the time readFromData:
    was being called, so I was successfully sending the message to a nil
    object (the inner Java programmer in me finds this very weird!).  The
    initialisation order seems a bit weird to me - it would make sense if
    you had to programmatically construct your view, but since it's all
    handled for you, it seems strange that view initialisation isn't
    handled first.  Why is this?

    So instead, I'm retaining the NSData object passed into readFromData:
    and creating the NSImage object from it in awakeFromNib: and passing
    that to the view for display.  That works.

    Is that the best way to do it, or can anyone think of a better solution?

    Thanks again for taking the time to help me out.

    best regards,

    Andrew

    On 26 Nov 2007, at 02:39, Jerry Krinock wrote:

    >
    > On 2007 Nov, 25, at 7:25, Andrew Ebling wrote:
    >
    >> So my question is, without subclassing NSDocumentController (which
    >> I understand you are not supposed to do), how do I tell
    >> NSDocumentController to simply hand me back an instance of
    >> NSImage?  Or is it even already doing this for me and all I have to
    >> do is get the class out of NSData?  If so how?
    >
    > Well, first of all, you can subclass NSDocumentController if you
    > have a need to.
    >
    > Regarding the Cocoa Document Architecture (which as far as API is
    > concerned means NSDocument and NSDocumentController), it handles
    > things like open and save dialogs, associating them with windows,
    > splicing them into the responder chain, document change counts for
    > undo/redo, all the "generic" document stuff.
    >
    > But the Cocoa Document Architecture does not do stuff with the data
    > in the file.
    >
    > So, now that you understand that the problem is probably not with
    > your usage of the Cocoa Document Architecture, that brings us to
    > your code...
    >
    >> NSImage *img = [[NSImage alloc] initWithData:data];
    >> [imageView setImage:img];
    >> [img release];
    >
    > I've never done that, but it looks like it should work.  To
    > troubleshoot, start by doing an NSLog on 'data' to see if it is
    > what's in your file.  Then NSLog the description of img, then
    > imageView, etc., etc.
  • The initialization order seems a bit weird to me - it would make sense if
    you had to programmatically construct your view, but since it's all
    handled for you, it seems strange that view initialisation isn't
    handled first.  Why is this?

      The short answer is because Cocoa's document architecture is best used within an overall Model-View-Controller design.  Within MVC, NSDocument is part of the Controller layer.  The document's user interface is part of the View layer.  What you seem to be missing in your approach is a Model layer.
      http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals
    /CocoaDesignPatterns/chapter_5_section_4.html

      http://developer.apple.com/documentation/Cocoa/Conceptual/AppArchitecture/C
    oncepts/DocumentArchitecture.html



      It is generally considered "bad" to store application data in user interface objects.  If the only place you store your image is in an NSImageView, how will your application work if no View is presented (and therefore no nib loaded and no NSImageView created)?

      The essential data of your application (in your case an image) should be stored in the Model.  All operations to manipulate the data should be implemented in the Model.

      Think of it this way.  NSDocument must work even if no graphical View is ever displayed.  Methods like readFromData:ofType:error:  are intended to initialize the Model - NOT THE VIEW.  In most applications, unlimited processing and data manipulation are possible within the Model even if no graphical user interface is ever shown.  Think about scripts that manipulate Model data within documents.

      There may be many Views that all access the same Model.  There may be zero, one, or many such Views presented to a user at any time.  Therefore, it is the job of the Controller layer to exchange data (in your case an image) between the Model and zero or more Views for display.  You can implement the exchange of data within your NSDocument subclass or via "bindings" or in an NSWindowController subclass or in any other Controller object you find appropriate.

      As an aside, thank you for this question.
  • > As you suggested, the code I posted was actually fine.  The problem
    > was that the view had not been initialised by the time readFromData:
    > was being called, so I was successfully sending the message to a nil
    > object (the inner Java programmer in me finds this very weird!).
    > The initialisation order seems a bit weird to me - it would make
    > sense if you had to programmatically construct your view, but since
    > it's all handled for you, it seems strange that view initialisation
    > isn't handled first.  Why is this?

    If the process is in this order you can actually base the
    initialization on the data you receive, or actually even decide not to
    do any initialization at all. You say it does make sense if you want
    to do everything programmatically. The best way to implement something
    automatically is to do it in the ame order as one would do it
    programmatically. In that case it is much easier to choose which way
    to use, or maybe use a combination of doing some things
    programmatically and let the framework/IB deal with other parts.

    > So instead, I'm retaining the NSData object passed into
    > readFromData: and creating the NSImage object from it in
    > awakeFromNib: and passing that to the view for display.  That works.
    >
    > Is that the best way to do it, or can anyone think of a better
    > solution?

    I would have retained the NSImage you got from the NSData as that more
    closely represents your data model. Another way to do this would be to
    use bindings to place the image in the imageView.

    Cheers, Patrick
previous month november 2007 next month
MTWTFSS
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
Go to today