2

I'm creating a 'test' document based application to learn more about how they work. I want to load an array that I saved earlier.

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
    NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSArray *array = [string componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];

    documentTitle.stringValue = [array objectAtIndex:0];

    // For testing
    NSLog(@"%@", array);
}

I loaded the file and got the data. I converted the data into an NSString. The file has multiple lines and every line has its own value. The first line is the document title. So I created an NSArray that put every line separately in the array. Then I set the first line of the document to the documentTitle label. For testing purposes I created an NSLog function too. It has to log the array.

The problem is that when I run the code, it doesn't do anything at all. The documentTitle.stringValue doesn't change. But the strange thing is that it does log the array:

2015-09-22 16:57:40.098 DocBased Test[10765:277445] 
(
    "The awesome title!",
    "This is a document!"
)

I declared the documentTitle in the Document.h like this:

#import <Cocoa/Cocoa.h>

    @interface Document : NSDocument
    {
        IBOutlet NSTextField *documentTitle;
    }

@end

If you need to view the project in Xcode here's a link: The link.

Does anyone know how to change the stringValue of the documentTitle (or any) label in the Document class?

Developer
  • 406
  • 1
  • 4
  • 18

2 Answers2

1

Luckily this is a common problem :)

Identify the .xib file that contains your NSTextView. Say for example it's called MyViewController.xib.

In the Interface Builder, open the Utilities panel (the right panel, the button is in the top-right). In the bottom of the utilities panel, click on the icon of the circle w/ a square in it, and search for "Object"

enter image description here

Now click and drag that Object into the left side (The Document Outline), below the "Placeholders" section.

enter image description here

This will create a new section called "Objects".

Click on your new object in the Document Outline. Now back in the Utilities panel, go to the "Identity Inspector"

Change the "Class" to be Document

enter image description here

Now go to the "Connections Inspector" also in the Utilities panel. Your (IB)outlets for Document should appear here, they'll look something like this:
enter image description here

Except instead of "nextKeyView" you will see documentTitle

Click and drag on the circle to the right of the outlet, and let go on top of your NSTextField that you want to be your documentTitle.

Now let's talk about what's going to happen.

  • First you will instantiate your MyViewController with a method like initWithNibName:...
  • Note that the nib that is backing the MyViewController.xib will not be loaded yet (this is called lazy loading)
  • When you try to access the view property of your NSViewController, it will load the nib associated with it
  • The nib is loaded, the objects inside of it are all instantiated
  • The init methods are all called on each of these objects
  • Outlet connections are now made, including your connection to your Document object to your NSTextView
  • -awakeFromNib is sent to all objects that were instantiated through the nib

At this point, your property documentTitle is now referencing a valid NSTextField object, and is no longer nil. You can now set it's stringValue as you please

Hope this helps!

A O
  • 5,516
  • 3
  • 33
  • 68
  • @A O Unfortunately, it's still nil. I noticed when you did it the outlet in the Connection Inspector shows under `Referencing Outlets`. Mine shows under `Outlets`. Can that be the issue? And I'm using Xcode 7 (beta 5) and I'm not using OS X storyboards (maybe that's useful). If that's not it, it's my Xcode again. I've been experiencing weird errors with it. I often watch tutorials on YouTube that are correct but the code just doesn't work when I try it. – Developer Sep 24 '15 at 18:53
  • 1
    No sorry, I was too lazy to create a new project but it should be in the `outlets`. Would you like to just upload your project to dropbox or something, and I can take a look? – A O Sep 24 '15 at 18:54
  • I uploaded it to MediaFire. The download link is: [https://www.mediafire.com/?z6drw20suvt0re0](https://www.mediafire.com/?z6drw20suvt0re0). It's a safe download of course. MediaFire didn't let me upload folders so it's a .zipx file. You can open it with the app iZip (free from the AppStore). I also put in a test file for my application to load. And thank you for your interest in this question. I'm really grateful. – Developer Sep 24 '15 at 19:27
  • 1
    Could you just right click the project folder and select "Compress *projectfoldername*"? This will put it into a xib-- I'd just rather not download an app. And not a problem, it helps me learn when I try to explain things :) – A O Sep 24 '15 at 19:29
  • [Here's the link](https://www.mediafire.com/?4z8d005q2wz0ici). I didn't even know you could compress a folder. Great shortcut. – Developer Sep 24 '15 at 19:34
  • [And here's a test document](https://www.mediafire.com/?w9687w2ztzynnt2). I forgot to upload that. – Developer Sep 24 '15 at 19:38
1

Given your project @ https://www.mediafire.com/?z6drw20suvt0re0,
I was able to get it working. Writing a new answer because my original is already really bulky, and fits more of a general case; whereas this answer will be very specific with your code

Only a few changes:
1) Because your Document.xib's owner is Document, you do not need to drag in the Document Object into your Document Outline. So I deleted that.
2) I selected the File's Owner (Document), and set up the connection to your NSTextField property
3) I removed the ivar

@interface Document : NSDocument
{
    IBOutlet NSTextField *documentTitle;
}

so your header now looks like this:

@interface Document : NSDocument

@end

4) I created the property inside of your implementation file (Document.m) as well as an NSString property (I will explain that in a second)

@property NSString *titleString;
@property IBOutlet NSTextField *documentTitle;

5) So if you go ahead and override the -awakeFromNib method (Just put an NSLog or something in there), you will see that in fact -awakeFromNib is being called AFTER -readFromData. Now remember what I said-- that the object is instantiated, but no connections are made until the view is loaded and the -awakeFromNib message is sent.

So what happens is this:

  • You click "Open" and select a file
  • Your Document is instantiated
  • -readData... is called immediately during instantiation, but the connection to the NSTextField isn't made yet, so it's nil
  • -awakeFromNib is called, and the connections from Document to any NSViews (including your NSTextField) are made
  • but it's too late.

So the solution is simple,
in the -readData method, instead of setting your NSTextField's stringValue, you should just save the string you read into a NSString property (What I mentioned earlier)

Then when your -awakeFromNib is called, you can just access that NSString property for the value you want, and set the stringValue then.

Here are the two methods that were changed:

-(void)awakeFromNib
{
   if( self.titleString )
   {
      [self.documentTitle setStringValue:self.titleString];
   }
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
    // Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO.
    // You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.
    // If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded.

    NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSArray *array = [string componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];

    NSLog(@"\r%@", array);


    self.titleString = [array objectAtIndex:1];

    return YES;
}

Let me know if you would like more explanation anywhere, but this will work for you :)

A O
  • 5,516
  • 3
  • 33
  • 68
  • Can you upload it to a file share website so I can download it (if it doesn't waste your time)? And thank you for finding a solution. I've been struggling over it for half a year now so it's good to know how it's done. I appreciate it. – Developer Sep 24 '15 at 20:09
  • Sure, here you go: https://www.dropbox.com/s/0lj1i1ezmv0u9ix/theTestApp%202.zip?dl=0 But I'd recommend you read my answer over and try to get it working for yourself-- it really isn't too bad any it'll help if you understand the underlying reason why it wasn't working – A O Sep 24 '15 at 20:12
  • Okay, I'll do. Thanks for your time! – Developer Sep 24 '15 at 20:18