I need my class, call it Document, to be the file owner of a nib containing the user interface (just as Document: NSDocument is the file owner in the Apple's document-based template). Likewise, I do not want Document to inherit from NSWindowController, but to manage an instance of it that will load the nib and set its Document as the file owner:
let windowController = NSWindowController(windowNibName: "DocumentWindow", owner: self)
Sounds simple, yet I am unable to get the file owner to deallocate along with all the other nib objects. The reason for this is that, according to Apple, "For historical reasons, in OS X the top-level objects in a nib file are created with an additional reference count." (see: Top-level Objects in OS X May Need Special Handling)
If I set the window's isReleasedWhenClosed to true and set the property itself to nil, then its deinit is called. Similarly with the instance of the NSWindowController... but the instance of Document, the file owner, remains! It's deinit is never called.
If I set the file owner to be the window controller, everything deallocates fine, including the Document instance, but this setup is difficult to subclass, which is what I need... I need the Document instance to be the file owner.
And so, for my case, the documentation suggests the following:
If the File’s Owner is not an instance of NSWindowController or
NSViewController, then you need to decrement the reference count of
the top level objects yourself. You must cast references to top-level
objects to a Core Foundation type and use CFRelease.
But how can I do this in Swift?
Would be grateful for any insight and suggestions!
Related
I'm new to the NSDocument architecture and am looking to set up multiple windows (and hence multiple NSWindowController objects) for a single document.
From what I understand, NSDocument was really created to work with a single window, and it seems that the ability to have multiple windows was shoehorned in later. For example, it seems that the NSDocument should always be the file's owner for any window's NIB files. But what if I wanted to separate the window controllers from the document?
For example, in the NSDocument subclass I am currently using the code:
- (void)makeWindowControllers {
[self setMyWindowController1:[[WindowControllerType1 alloc] initWithWindowNibName:#"MyWindow" owner:self]];
[self addWindowController:[self MyWindowController1]];
}
But the NIB file "MyWindow"'s file owner is set to the NSWindowController subclass (WindowControllerType1), NOT my NSDocument subclass. In this case, whenever I look to get the document by using [[NSDocumentController sharedDocumentController] currentDocument], this ALWAYS returns nil.
I figure this can be rectified if I set the NIB file's owner to the NSDocument subclass, but then all of my outlet links break, and I'm not sure how to link to the NSWindowController subclass (WindowControllerType1), as the typical course of action (as far as I can tell) is to make the NSDocument a window controller delegate as well, which I would like to avoid!
Any suggestions?
EDIT:
Let me clarify and add some new information. I am aware of Apple's position on using the WindowController's document property. However, as I plan of having a larger number of nested NSViews in each window, I want to avoid passing the document through a large chain of views in order to accomplish this.
The issue is not necessarily this chain. It is mostly that when the [[NSDocumentController sharedDocumentController] currentDocument] is ALWAYS nil, none of the "for free" features of NSDocument seem to work, such as undo/redo. This is the major issue that I need to resolve.
From what I understand, NSDocument was really created to work with a single window, and it seems that the ability to have multiple windows was shoehorned in later.
No, makeWindowControllers is available in OS X v10.0 and later.
But what if I wanted to separate the window controllers from the document?
The window controller owns the NIB.
Any suggestions?
Do
[self setMyWindowController1:[[WindowControllerType1 alloc] initWithWindowNibName:#"MyWindow"]].
NSWindowController has a property document which is set by addWindowController:.
Use document property of NSWindowController instead of currentDocument.
I'm trying out Cocoa (on OS X Mavericks) with a NSDocument-oriented app with a (planned) WebKit control. I noticed that the "New" menu command is disabled if all the document types are Viewer or Other. I'm making a web browser, so the app shouldn't be an Editor for anything, yet that's what I need to enable the New command.
I guess I need to override something, but where? The obvious candidates are the NSDocument subclass, a NSDocumentController subclass, or an application delegate. The first one is the only one I actually have so far (i.e. included by Xcode's default code), but it doesn't seem appropriate for NSDocument instances to create new ones. So I would have to create a NSDocumentController or NSApplicationDelgate subclass to do this, right?
So much of a newbie that I didn't completely RTFM.
The new-document command is handled by NSDocumentController. That class has a defaultType method that's used to determine which type a new document should be. By default it chooses the first type in the (static) list of document types that is set to "Editor" mode. It returns nil otherwise (i.e. Viewer and Utility apps).
A non-editing app can gain new documents by creating a subclass of NSDocumentController that at least overrides defaultType to return a non-nil string describing the document type for new documents. I used a MIME type, but I guess UTIs (and maybe extensions) also work.
But there's a new problem: your NSDocumentController won't be created by default; the default App setup ignores the class and makes a NSDocumentController object anyway. There's supposedly two ways around this: put an object of your subclass into the app's first NIB, the one with the main menu bar; or directly set the document-controller attribute to a new object of your subclass in one of the initialization methods of the application delegate. (Since the default document-based project templates don't include an application delegate class, you have to create that too.)
But I heard that either method is uncertain; if any previous part of application startup touches the NSDocument system, a default document-controller will be created and block out your object. Are there any Cocoa gurus out there that know where to put a custom NSDocumentController subclass object so it can't get pre-empted by a default document-controller?
...
I tried putting what I said in the third paragraph to work, but it doesn't. I keep getting "Date Time MyApp[Number1:Number2] The XXX type doesn't map to any NSDocumentClass." from the NSLog debug screen, where XXX is the string I return from defaultType. Tried both MIME types and UTIs. I'm stumped for the moment.
I am new to Cocoa and I don't understand the concept of File's Owner of a .nib file.
Here is the way I would see things :
Consider a file myNibFile.nib file that describes how a window looks.
Now, I want to connect an actual window to this .nib file. So, I create a class myWindow, which is a subclass of NSWindowController. And, to do this connection, I change the init method like this:
-(id)init
{
[super initWithWindowNibName:#"myNibFile"];
return self;
}
So, I understand that when I create an instance of myWindow, the "system" will go and look at the .nib file and create the adequate object.
So, my question are :
why do I have to specify that the File's Owner of my .nib file is myWindow ? Isn't it redundant ?
I guess it means I didn't really understood what the File's Owner. What is it ? Why does the .nib file have to belong to something ? Can't it be "somewhere" in my "application" and when it is needed, the "system" goes there and use it ?
Thanks for helping me to see more clearly in these new concepts !
Two points to be remembered:
The File owner is the object that loads the nib, i.e. that object which receives the message loadNibNamed: or initWithNibName:.
If you want to access any objects in the nib after loading it, you can set an outlet in the file owner.
So you created a fancy view with lots of buttons, subviews etc . If you want to modify any of these views / objects any time after loading the nib FROM the loading object (usually a view or window controller) you set outlets for these objects to the file owner. It's that simple.
This is the reason why by default all View Controllers or Window Controllers act as file owners, and also have an outlet to the main window or view object in the nib file: because duh, if you're controlling something you'll definitely need to have an outlet to it so that you can send messages to it.
The reason it's called file owner and given a special place, is because unlike the other objects in the nib, the file owner is external to the nib and is not part of it. In fact, it only becomes available when the nib is loaded. So the file owner is a stand-in or proxy for the actual object which will later load the nib.
Hope you've understood. I'll clarify any of the points if you ask.
The fundamental thing to understand is that Interface Builder allows you to create objects that are automatically connected to each other, with no effort on the part of your program. You can instantiate all kinds of objects including non-view ones, and they can be inter-related; for example, you might create the instance of a table view data source along with the view itself, etc. This mechanism is commonly used to create an Application Delegate within your Main Menu NIB.
However, since it's all done via drag&drop, it seems there is no way that you can form a connection between any of the NIB objects and the objects that already exist in your application, with one exception.
When code is loading the NIB file, you have the option to specify exactly one object which the NIB will consider to be the "Files Owner". This is the placeholder that you see inside Interface Builder; since it can represent any object within your application, Interface Builder can't know what actions/outlets are available on it. This is why you can modify the "class" of the Files Owner, in the Attributes tab.
Files Owner does not really represent "ownership" or "parenthood". What it represents is "the object that loaded this NIB".
File's Owner is a placeholder in IB so all the outlets and actions in your code are "linkable" in IB, you can control-drag to connect stuff on the screen to the code.
File's Owner in Interface Builder is so that it knows the object type of the parent. This is used in two ways. Firstly, in Interface Builder so that IB knows what outlets and actions are available to you to connect. Secondly it is used by the application framework to know how to reconnect things to the rest of your code once the nib file is loaded.
My app - a document based Core Data app - is going through a second iteration and now needs multiple windows to manage several model objects. Currently it manages Events and Locations through one window and one controller. The standard generated document class acts as controller for the main window at the moment.
I now want a separate window for managing Locations model objects. It seems good design to have a separate controller (NSWindowController) for each window, but then I realised that these controllers will not have access to the Managed Object Context, which is required to access the model objects.
What is the best approach here?
EDIT:
I followed ughoavgfhw solution as follows:
Created a new XIB for Locations and added an Array Controller to load Location objects
Created a custom controller ManageLocationsController as a subclass of NSWindowController
Made the custom controller the File Owner in Locations XIB
Mapped the Array Controller's context to File Owner and keyPath document.managedObjectContext
I open the Location window with:
ManageLocationsController *aController = [[ManageLocationsController alloc] initWithWindowNibName:#"ManageLocations"];
[aController showWindow: self];
This is done from EventDocument, which is the default class generated by XCode.
When mapping the Array Controller, this left a round black exclamation mark in the keyPath field and when I open the Location window it throws an exception saying "cannot perform operation without a managed object". Obviously not good. What am I missing?
Using custom window controllers is the best way to do this. A window controller might not have direct access to the managed object context, but it has access to the document, which does. You can access it programmatically using windowController.document.managedObjectContext or from bindings with the key path document.managedObjectContext. If you want to simulate direct access to the managed object context, you could create a readonly property which loads it from the document.
// header
#property (readonly) NSManagedObjectContext *managedObjectContext;
// implementation
- (NSManagedObjectContext *)managedObjectContext {
return self.document.managedObjectContext;
}
+ (NSSet *)keyPathsForValuesAffectingManagedObjectContext {
return [NSSet setWithObject:#"document.managedObjectContext"];
}
The keyPathsForValuesAffectingManagedObjectContext method is used to tell the key-value observing system that any object observing the managedObjectContext property should be notified of changes whenever the paths it returns change.
In order for the window controllers to work properly, they must be added to the document using addWindowController:. If you are creating multiple windows when the document opens, then you should override makeWindowControllers in your document method to create the window controllers, since this will be called automatically at the right time. If you are creating window controllers upon request, you can make them in whatever method you want, just be sure to add them to the document.
[theDocument addWindowController:myNewController];
As for the little black exclamation mark in IB, you will just have to ignore that. The document property of NSWindowController is defined with the type NSDocument, but the managedObjectContext property is defined by the NSPersistentDocument subclass. IB is warning you that the property might not be there, but you know it will be so you can just ignore it.
When a Cocoa NIB file instantiates an instance of a custom controller object, what is the name of the variable that that custom controller instance is assigned to?
In case that isn't clear, if you manually created an instance of that class you would do:
MyControllerClass *myVar = [[MyControllerClass alloc] init];
What equivalent of "myVar" has the NIB used when doing this behind the scenes?
There is no such thing as a variable name once the app is compiled, so this question doesn't make much sense. In your example, myVar is just a convenient label for you, the programmer, and does not exist in any way once your source code is compiled into binary code.
When you place an object into a nib file, it is archived and then unarchived at runtime. If you want to be able to get a reference to an object that has been archived in a nib file, you need to use an outlet, which means you declare an IBOutlet instance variable in a class that is present in the nib file and then connect that outlet to the object in the nib you want to reference in Interface Builder. Instance variables are different to the stack variable that you declared in your example and can be referred to at runtime.
Typically you would have an object that "owns" a nib. Normally nibs are loaded by an instance of NSWindowController or NSViewController and window or view controller is represented in the nib file as File's Owner. If you declare outlets in your window/view controller, you can then connect the outlets from File's Owner to your object in Interface Builder.
So, to clarify, you need a reference to your object in the nib from some other object in the same nib. That second object declares an outlet using the IBOutlet keyword on an instance variable like so:
#interface SomeOtherObject : NSObject
{
IBOutlet SomeObject* anObject;
}
#end
In Interface Builder, you can then connect the anObject outlet of the SomeOtherObject instance to the first SomeObject instance. You can do this by control-dragging from one object to another or you can do it in the connections panel in the Interface Builder inspector.
You can then refer to your SomeObject instance by the variable name anObject inside the code for SomeOtherObject.
Implement the awakeFromNib method in your controller class - it's called immediately after the nib has finished loading, and your controller's instance can be found in the "self" variable.
# tedge (I can't make comments to your answer):
Can you clarify for a beginning Cocoa learner a bit. Take the Apple Currency Converter tutorial.
I implement the awakeFromNib method in the existing ConverterController class. (Something I will be learning to do shortly!)
The app starts up and an instance of ConverterController is automatically instantiated.
What will awakeFromNib tell me about that running instance (other than that it's ready to use) -- and what syntax with "self" gets it to divulge that information?
… what is the name of the variable that that custom controller instance is assigned to?
It's whatever name you gave that variable when you declared it.
IB doesn't create variables for you. It sounds like you're after an outlet, which is a variable you create that IB knows about and lets you plug objects into, thereby setting the variable.
(You actually can create outlets from IB, and in the modern runtime, this should really create the outlet, not just declare a non-existent outlet in the nib. Even this way, though, you create the outlet [in IB] and you give it a name.)
I think what's Nibbles is confused about is that how to reference the variable defined only in NIB file from code.
the answer to that is, normally you have a custom controller class (or delegate class) A in code and NIB, and if you have another class or controller B only defined in NIB, just setup a outlet in A pointing to B. Since A can be used anywhere in your code, B can be accessed as well through A then.
I had this question as well.