I've added a custom subclass of NSWindowController to my Cocoa project, and added an instance of my subclass to my application's nib. I expected to see my override of the -initWithCoder: method called when the nib was loaded, but it was not. To debug, I added a regular -init method and set a breakpoint on it — and sure enough I hit the breakpoint while loading the nib.
This could actually make some things simpler for me (e.g. setting the windowNibName) but I don't understand why Cocoa is behaving this way. All the documentation I have read suggests that -initWithCoder: is where I should be overriding. Why is this case any different?
I'm assuming that to instantiate your window controller in Interface Builder, you dragged a generic NSObject instance to the nib file, then assigned your custom NSWindowController subclass as the object's class, is that correct? If so, then I think they key difference going on here is that you're dealing with instantiating a generic object rather than a custom object included in one of IB's palettes.
Most of the time, when you create and configure an object using IB, the settings that you specify in the various inspectors gets encoded using the encodeWithCoder: method when the nib file gets saved. When you then load that nib file in your application, those objects get initialized using the initWithCoder: method.
However, in the case of that generic object instance, Interface Builder doesn't necessarily know anything about the class of the object being instantiated. Since you can specify any class name at all to be instantiated, if you specify a class that IB doesn't have loaded via a palette or framework, there's no way it can serialize that object using NSCoding. So I believe that when you instantiate a generic object like that, it gets initialized using init rather than initWithCoder: because it wasn't saved using encodeWithCoder: in the first place when the nib file was saved.
I don't know if this is documented anywhere, but I think that's why you're seeing a difference there. I also don't think it's specific to NSWindowController, but rather you'd see the same behavior from any object instantiated as a generic NSObject in IB, regardless of the specific class.
I still don't have a formal answer why Cocoa behaves this way, but in practical use it seems to be convenient. I have defined an NSWindowController subclass with the following -init method and it works like a charm.
- (id)init;
{
if ((self = [super initWithWindowNibName:#"MumbleMumbleSheet"]) != nil) {
…
}
return self;
}
If -initWithCoder: were being called I would have to figure out how to fulfill the implicit obligation to call the super -initWithCoder: method and still get the right -windowNibName used for loading. It's much more straightforward this way.
I would still appreciate a pointer to some documentation that says this class is different and explains why and how… But in the absence of documentation there is empirical evidence.
The coder methods are used for classes that have been serialised and saved to file.
What you are doing here is different. You are building your controller class into your executable. This means that there's no need to read the class itself from file as it's a part of the running application binary.
When using this controller class you need provide an init method where you provide the nib file name. Why? Well, you have the compiled class as a part of your exe but no knowledge about what the nib file is. This is how you provide that knowledge.
Think of it this way. You controller class is a part of the exe. Some link between it and the nib file needs to be made. One way would be to scan through all the nib files looking for references to this controller. That would be inefficient. Provide the name in the init and everything bootstraps.
In other words you have learnt some important lessons from your experiments. Well done, on being so observive.
Related
I'm sure this has been asked before in a better way, but I'm trying to understand how interface elements relate to classes. My application is very simple, mainly an NSTextView that gets populated with some output logs.
I've been just using AppDelegate to handle most of my app's function, but now I want to break things up a little. So I have created a Logger class that has all the code relating to the logging part, including handling updating the NSTextView. So what I'm not sure about is should I hook the NSTextView's referencing outlet directly to the Logger class, or should I keep these two things decoupled and just pass a reference of the NSTextView to the Logger object?
Your Logger, on top of it's logging logic, sounds like it also contains presentation logic-- which just takes your model (data), formats it, then directly passes it to your NSTextView to display it.
It sounds like your NSTextView is inherently connected to your Logger. If you connect your NSTextView to an outlet in your Logger, you will be able to easily set the stringValue of the NSTextView through that reference.
Also, as you said the alternative would be to have another object obtain a reference to your NSTextView, then pass it to your Logger. Because your Logger controls the presentation of your NSTextView, that wouldn't be necessary. In fact, it should be the other way around-- you can make your IBOutlet public (by placing it in your header file, instead of in the class extension in the implementation file), if any other objects need a reference to your NSTextView (Like a window that wants a reference to that view to display)
So I believe you do in fact want to connect the two
I know the question is a bit generic but I guess my issue is generic as well.
I'm developing a small application in my free time and I decided to do it with Cocoa. It's nice, many things works almost automagically, but sometimes it's quite hard to understand how the framework works.
Lately I'm facing a new problem. I want to manage all the windows of the application from a single class, a front controller basically. I have a main menu and an "Import data" function. When I click it I want to show another window containing a table and call a method for updating the data. The problem is that this method is inside the class that implements the NSTableViewDataSource protocol.
How can I have a reference to that class? And more important, which should be the right way to do it? Should I extend the NSWindow class so that I can receive an Instance of NSWindow that can control the window containing the table (and then call the method)?
I may find several ways to overcome this issue, but I'd like to know which one is the best practice to use with cocoa.
PS: I know that there are tons of documentations files, but I need 2 lives to do everything I'd like to, so I thought I may use some help asking here :)
The problem is that this method is inside the class that implements the NSTableViewDataSource protocol.
How can I have a reference to that class?
These two sentences don't make sense, but I think I understand what you're getting at.
Instead of subclassing NSWindow, put your import window's controlling logic – including your NSTableViewDataSource methods – into a controller class. If the controller corresponds to a window, you can subclass NSWindowController, though you don't have to.
You can implement -importData: as an IBAction in your application delegate, then connect the menu item's selector to importData: on First Responder. That method should instantiate the import window controller and load the window from a nib.
In your import window controller's -awakeFromNib or -windowDidLoad method, call the method which updates the data.
Added:
Here's the pattern I'd suggest using in your app delegate:
#property (retain) ImportWindowController *importWC;
- (IBAction) showImportWindow:(id) sender {
if (!self.importWC)
self.importWC =
[[ImportWindowController alloc] initWithWindowNibName:#"ImportWindow"];
[self.importWC refreshData];
[self.importWC.window makeKeyAndOrderFront:sender];
}
The title question arises for me when working in many areas of the application: models, controllers, getters, setters, actions, etc. I have a Core Data document-based application and I constantly need to get a reference to the current NSMangedObjectContext object.
The current scenario involves a custom controller I made to handle "simulations" (this application simulates a particular kind of mathematical model.) There is a button called "Simulate" and it is bound to an action in the simulation controller. The simulation controller needs to get information from the current document i.e. information from the NSManagedObjects in the current managed object context.
The simulation controller is a subclass of NSObjectController which has a method called managedObjectContext but when I call that method, I get nil. I'm not sure why nil is returned but I do know that the controller is not acting on behalf of any managed objects. It's controlling the simulator, which is not a model in MVC. The controller is an interface between the views, models, and the simulator.
How do I get the NSManagedObjectContext that is storing NSManagedObjects for the currently open window? The currently open window has views showing information from objects in the context, and the simulation button is in this window's toolbar.
Update:
Well... thanks to TechZen for opening my mind a little... or maybe it was just taking a break and going to a BBQ...
For this particular scenario the answer is:
Bind the managed object context in Interface Builder to my controller (the controller was created in interface builder and then I changed the class to be my subclass of NSObjectController). This setting can be found in the Bindings Inspector under Parameters and is called Managed Object Context. I set it to bind to the File's Owner and the model key path to "managedObjectContext".
Then, the message "managedObjectContext" works within my controller just like:
[self managedObjectContext];
This, however, still doesn't fully answer my question because I'd also like to get a reference to a managed object context in a class method of a subclass of NSManagedObject. So if the simulation controller then creates a new NSManagedObject by calling a class method on my subclass of NSManagedObject, I'd like that method to have a reference to the context and create the object. And I do not want to pass the context to the method... I feel like the class methods should be able to get the context... I remember seeing code like:
[[NSApp delegate] managedObjectContext];
But this didn't work for me. But if it did, this would be an excellent solution becauxe NSApp is global and returns the current NSApplication instance.
Update:
Well, after a lot of reading and Googling... I discovered that I was just totally off the mark in the design of my application. Instead of having a simulation controller receive the click of the simulation button, I created a custom NSWindowController for that window and it receives the simulation button click event (a kind of IBAction). NSWindowControllers have a reference to the NSPersistantDocument, which has a reference to NSManagedObjectContext. And this custom window controller I wrote get's that NSManagedObjectContext and passes it to the simulation controller... and the world is filled with joy. Not sure how useful this is to others, since this question and answer are now littered with noise.
If you are using a Core Data document based application, then each document will be an instance of NSPersistentDocument which will have its own NSManagedObjectContext for just that single document.
You can get the context by:
NSManagedObjectContext *context=[aPersistentDocument managedObjectContext];
Update:
With a document based app, you don't have a single, central or master managed object context because each document has it's own wholly independent Core Data stack from the store to the context. You end up with as many context has you have open documents.
This contrast with a more database like design in which the entire app has only those stores and context defined when the app was coded. In that case, you may have only one context that is used everywhere in the app. In that case, you can park the context in the app delegate and access it from anywhere.
It's bad design to have a class or instance method in a NSManagedObject subclass that finds a context because this limits the flexibility of the use of the subclass. To do so the class would have to be wired into the structure of a specific app so the subclass could only be used in a specific design where it could find the context. If you changed anything about the location of the context or the use of the subclass, you would have to write it all over again.
I think you need to back out and rethink your design and decide if you want a document based app or a more database-like app. I would recommend reading:
Document-Based Applications Overview and NSPersistentDocument Core Data Tutorial
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.
I'm working with CoreData in Cocoa (not document-based).
My problem is, that I want to access the SAME NSArrayController from different NIBs.
But every NIB has an own instance of this NSArrayController.
My question is now how I could generate sharedObjects (like the NSUserDefaultsController).
It would help me a lot. Thanks for your answers. =)
You generally don't want to share an NSArrayController between nibs. It's probably better to have multiple NSArrayController (one per NIB) which are all bound to the same underlying model. If you want this model (e.g. an NSArray) to be application global, you can expose it via the NSApplication's delegate (e.g. instantiate your custom MyAppDelegate class in MainMenu.nib and connect the NSApplication's delegate outlet to the instance of your MyAppDelegate class). In other NIBs, you can then bind an NSArrayController's contentArray binding to Shared Application.delegate.myArray (assuming MyAppDelegate exposes—via KVC-compliant methods—an NSArray binding called myArray). You are essentially using IB and the MainMenu.nib to create your singleton instance of MyAppDelegate.
Keep in mind that this approach makes unit testing your application difficult, since there are now singletons in the object graph that you can't mock or stub out during testing. It would be much better to create an NSWindowController or NSViewController for each secondary (non MainMenu.nib) NIB and bind the NSArrayControllers in those nibs to File Owner.myArray. You can then instantiate the NSWindowController or NSViewController, passing it an array (or array KVC-compliant object) before loading the secondary NIB. In this way, you can test the functionality of the nibs in isolation (using a mock or stub for the array).
I'm not really sure trying to reuse NSArrayController is the best choice (I'd need to know more about your project, but I've never ran into a situation where I'd do something like that), but you can use a static variable inside a class method like so:
+ (id)sharedObject;
{
static id object = nil;
if ( object == nil )
{
object = [[self alloc] init];
}
return object;
}
Keep in mind that this is not a true singleton, since you can still allocate additional objects of that class. You can use this guide if you really want to be strict.
Matt Gallagher has a good post on singletons and other ways to have "global" data over on his blog you may want to check out too. It's a little more clear than Apples documentation, and has a link to a header file that makes it nice and easy to create singletons out of almost any Cocoa class.
I'm actually using his header file in some of my projects, and it works great.