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];
}
Related
Good evening all,
I'm slowly working through my first OS X app. I have been having a hard time getting my Swift class to interact with an NSPopUpButton. Just to make sure I was doing it right, I created a new project and successfully erased all entries and entered text into the NSPopUpButton via AppDelegate. However, as soon as I try to move the same functionality to my own class, I can't even get the IBOutlet connection across to the new class.
Is a particular subclass type required of a new class to work properly with interface builder?
Here is a screenshot of the class I have created, as well as AppDelegate where I am trying to call the function belonging to this class.
Finally, here is the IB element in question, should I be able to select my own class under the 'Custom Class' inspector?
I am an iOS developer, but I would imagine the same principles would apply to your situation.
A ViewController class and an interface created in interface builder are two seperate things. While it may appear that they are connected via an iboutlet, they are actually independent and one can be instantiated without the other.
Currently, you are only creating an instance of your ViewController class in your App Delegate - and that's all. The system has no idea that your xib even exists! The outlets will only be connected once your app connects your xib to your ViewController class.
How do we do this? It's actually quite simple. Instead of instantiating our view controller like this:
let viewcontroller = ViewController()
We would connect our view controller to our xib in the instantiation:
let viewcontroller = ViewController(nibName: "MainWindow", bundle: NSBundle().mainBundle)
The nibName is telling the system the file name of your xib, and the NSBundle().mainBundle is telling the system where to look for the xib.
This will all only work if your xib has been assigned a custom class, like you mentioned. In your xib in interface builder, select the entire view controller. Then, in the custom class inspector type in the name of your ViewController class (in your case: ViewController - it should autocomplete). This will make sure your outlets are connected.
And you should be set up!! Let me know if you have any more problems come up.
Good luck!
EDIT:
This replaces the first part of my answer, however the part about hooking things up in Storyboard remains true. Upon reconsidering, I've believe I've realized that we are only creating the view controller, and not adding it to our view. Despite this, I believe we can take a short cut solution by adding one method to your view controller subclass (the one we set in the Storyboard). Start typing in viewDidLoad, and it should autocomplete. Type in super.viewDidLoad() at the beginning of the method. After that, type self.listUpdate(). This should work if the classes are hooked up correctly in Storyboard. This means you can delete the variables you created in the App Delegate.
Reference: You might also find Apple's documentation on creating a view controller handy (it's in Objective C online, but can be easily converted to Swift - it's the concept that counts): NSViewController Class Reference
I have an NSArrayController bound to CoreData in my application. It is also bound to a TableView that displays the data. Two buttons are bound to the ArrayController that add and remove lines. All of this is working as expected. I can add, edit, save, and remove CoreData Entries.
There is a section of my app that is to accept drag and drop operations from files (working). It takes the data from the files, looks for various information, and is to insert this information into the Core Data database via the NSArray Controller.
I have added the class handling the parsing/adding of the file to the database as an object in IB. I created an IBOutlet for the array controller in the class, and bound the controller to the class' referencing outlet.
If I add a button to the interface to directly call the method that adds a custom record to the database, everything works. If the method is called via the drag and drop operation, nothing works, even logging a simple [arrayController className] returns null (though returns NSArrayController as expected when the method is called from the button click).
The only difference I can see is that when accessed through the button click, the method is called directly, while the other way passes through my drag and drop class before loading the parsing class, but I'm completely stuck on how to remedy this situation. I'll be happy to provide code, just not sure which code you'll need.
Any help is appreciated. Thanks!
==================
UPDATE
turns out I was connecting the IBOutlet to a class (a subclass of a view) object in IB instead of to the view itself handling the drops. Connecting these up made things work. Well, not work, I have other issues to iron out now, but the Array controller is now instantiated.
Moved from comment to answer: The array controller you are trying to add stuff is not instantiated. I assume you are not referring to your original NSArrayControllerinstance but maybe a new created one? Probably a problem of communication between your class instances.
Debugging this should be straightforward ... using the debugger. Set a few breakpoints (one at each action the button(s) call, and one at each point where your class instances are meant to talk to each other (your importer and your main controller)). Run, test, step through the code when the debugger breaks at each breakpoint.
My guess: An outlet is not hooked up (is nil) in IB or is not yet reconnected at runtime (see -awakeFromNib and make sure you're not trying to touch an outlet or action that hasn't been fully reconnected from the nib at runtime by the time you're trying to use it).
Something’s not hooked up right, BUT you don’t want to do it this way anyways. There’s no advantage to inserting via an NSArrayController. Just create new objects with NSEntityDescriptions:
+ (id)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;
And you’re done. If your NSArrayController is hooked up correctly it’ll auto-fetch the new objects at the end of the event so the user will see them “immediately.”
I appreciate you taking the time to read this and hopefully help me out!
I am writing my first program in Xcode 4, using Objective-C and Cocoa for the first time. I have been following the tutorials, but at this point I want to display custom graphics using a custom view, where the graphics change based on the values in my model object.
My controller class, GameController, based on NSObject, creates my model object during initialization, and successfully receives button action messages, updates the model object appropriately, and updates text strings on the window in return using its outlets. I accomplished this by creating an NSObject object in my xib file (which is called the default (?) name of MainMenu.xib, but it includes all the UI in my application), using the utilities pane to set its class to GameController, and then connecting its actions and outlets to the appropriate UI elements.
I have dragged a custom view into my window, and used the utilities pane to set its class to my custom view class, BoardView, which is based on NSView. In BoardView, I have overridden drawRect: to fill in the background of my view with a blue color, and then draw each of the graphics defined by my model class (GameStatus).
The problem is, I do not know how to access the data in GameStatus from my BoardView class, or have GameController update a member variable of BoardView. I have not instantiated BoardView anywhere besides in Interface Builder, by dropping a custom view on my window. I do not have a BoardView object in my xib file (the column or list on the left side in Interface Builder).
I tried to create an outlet to a BoardView object in my GameController class (which does have an object in my xib, as I mentioned above), and connecting that outlet to the BoardView custom view in my window, and Interface Builder seemed fine with that. However, when I run the program, the value of the BoardView outlet pointer in my GameController class is 0x0. It seems like the outlet is not being connected to a BoardView object. I don't know if I need to do something else in Interface Builder to make an actual object (I tried creating one in the list to the left, but then couldn't figure out a way to connect it to the actual custom view displayed on the window).
To add to the confusion, when I run my application, the BoardView area of the window will display the blue background, and in fact any other graphics which I define in the drawRect: function. However, without any way to talk to my model object, I can't change the graphics based on the state of the model. I'm not sure if the fact that the hard-coded graphics are displaying correctly means that there is an object there somewhere, or whether it is somehow drawing based on the general template of the class somehow. The latter doesn't really make sense to me, but if the former is true, I'm can't figure out how to talk to that object from other parts of my code.
I feel like I'm just missing some basic understanding of how Xcode / Interface Builder creates objects or something. I would really appreciate someone telling me exactly what I'm missing here in the connection between my MVC objects / classes.
Thank you in advance for any help you can give me!
EDIT 2011/09/06:
To download a copy of my project to take a look at it, go here:
http://talix.homeip.net/2011/rival/
That's my home server and I'm an amateur at this, so please leave a comment if it isn't working. Thanks! Also, if there is a better way to respond to comments other than editing my original post, please let me know; I'm also new to this website. ;-)
-Joe
It sounds like you're only instantiating one GameController and one BoardView, and that's happening in the nib.
This, then, is the heart of the problem:
I tried to create an outlet to a BoardView object in my GameController class (which does have an object in my xib, as I mentioned above), and connecting that outlet to the BoardView custom view in my window, and Interface Builder seemed fine with that. However, when I run the program, the value of the BoardView outlet pointer in my GameController class is 0x0. It seems like the outlet is not being connected to a BoardView object.
Where in your code are you needing a reference to your BoardView but getting nil? Is that in GameController's init method, or in some other method of GameController?
If it's inside init, this is what I'd expect. The code that loads nibs must initialize the objects before it connects the outlets.
(If it's outside of init, I'd suggest starting by disconnecting and reconnecting the outlet.)
View = BoardView
Controller = GameController
Model = GameStatus
In MVC, the controller usually brokers communication between the model and the view, so I suggest you handle it this way:
Add a gameController outlet to BoardView and connect it to your game controller.
In drawRect, have the BoardView get the game status from the game controller.
I also suggest you make GameController your application's delegate:
Delete RivalAppDelegate.[hm].
In your nib, delete the reference to Rival App Delegate, and connect the Game Controller reference to the File's Owner delegate outlet.
You've got two instance of BoardView in your nib. Hook up the one inside your window to the boardView outlet of GameController and delete the other.
In GameController.h, after #interface GameController : NSObject, add <NSApplicationDelegate>
In GameController.m, implement applicationDidFinishLoading and set up your application there. (You can even call setGameStatus there if you want to.)
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
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.