I'm beginning to think that my Cocoa application is not really done according to the principles of MVC. My problem is the following:
I have some classes in my project, one of called Copier.h and another called DropReciever.h. Copier is sort of my main view controller, as it has all the bindings and main methods. DropReciever is a custom implementation of an NSView to allow the app to accept files via drag and drop.
Now, is there an easy way to send messages to Copier from DropReceiver? Right now, the two don't know each other, and I can't think of an elegant way to connect them, since since they are both kinda instantiated seperately. How can I make them see each other? Or, is there an elegant, Coca-ish way to do this better?
(If you want to look at my source code, it's here.)
Another way would be to expose a property of the drop receiver as a binding, and bind the copier to it (programmatically). Then, in the drop method, have the drop receiver set the dropped content as the value of this property (which you would name something like droppedObject).
When you set the property, the magic of Bindings will set the bound property of your copier. The copier can react appropriately there in its setter method.
I would have a delegate property on the DropReceiver. Whatever is responsible for tying these things together would set the delegate. The delegate object can be an id, or you could create a protocol for it (both idioms are common in Cocoa). I do this all over the place. You get the separation you need, without having to work around the houses too much.
The only downside, if you don't set the delegate on initialisation, is that all your calls to it need to be protected by if( delegate ) checks.
The way I usually do it is to instantiate DropReceiver in the nib and then add an IBOutlet DropReceiver * to your Copier.h, then drag a connection from the Copier instance to your DropReceiver in the window
Related
I'd like to draw a proper, modern animated focus ring around a control, which according to Q&A 1785, should be a simple matter of overriding the -drawFocusRingMask and -focusRingMaskBounds methods.
Trouble is, for this project I have to use Xojo, which can declare and invoke Cocoa methods, but doesn't give me any opportunity to actually create my own view subclass.
So, is there any way to get a proper focus ring without making an actual subclass? Some other methods, perhaps introduced after this 10.7 tech note, that get the job done? Or some sneaky way to inject a method into an existing class at runtime?
As one comment suggested, class_addMethod() would be right if you want to add an optional protocol method. The public macoslib project has some code that shows how to do that, just search for that name.
However, if the function is already implemented, then you cannot add another. In that case method swizzling is the solution. It's a common method to replace a selector'd function address with another, and then call the original one.
I don't seem to have an example in Xojo for that at hand, though.
Update
For standard Cocoa controls the simplest solution is to set the NSView property focusRingType accordingly (available in macoslib). Implementing drawFocusRingMask is only necessary for custom controls.
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 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 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];
}
In my Cocoa app, I have a sheet with a one-column NSTableView that lists a bunch of files in a directory (the app makes back-ups of it's main database, provides this list to users so they can revert to a particular back-up). The content is loaded into and provided to the table view by an NSArrayController, each object is just an NSFileWrapper (I'm considering using NSURL instead, but I digress). The NSArrayController handles sorting, enabling the buttons when a row is selected via bindings, that's all great. I have an NSWindowController subclass object (BackupsSheetController) that hooks all this up and exists in the sheet's nib.
However, when a user edits one of the cells, I want to respond to that change from BackupsSheetController by appropriately re-naming the file represented by that cell, putting it in its new location. Since the table view is bound to the NSArrayController, I don't get sent the NSTableViewDataSource message – tableView:setObjectValue:forTableColumn:row:. If I set my BackupsSheetController as the datasource for the NSTableView object in the nib, I get sent that message sometimes, but not very often, to say nothing of every time.
Most questions and examples I see out there for this scenario handle this all by using a custom model class for items in their table view, and make some controller object an observer for changing properties that they wish to respond to. In other words, each item would be something like a BackupNode object, and BackupsSheetController would observe each for changes to the name property (or whatever I would call it). That seems totally overkill for my scenario, but I also don't want to ditch the bindings I've already got in use and I don't see another way to do this. Is there another way to do this, to make sure I reliably get the setObject:... message? Or should I drop the NSArrayController and make BackupsSheetController the delegate and datasource for the table?
In the "BackupNode" scenario, I don't see why BackupsSheetController would observe each for changes in its name. That's a very roundabout way of doing things. I would think that the hypothetical BackupNode object would simply do the necessary work in its setter for the name property.
Anyway, I recommend using proper model objects. When you try to build a model with only Cocoa-provided objects like NSFileWrapper, NSURL, or NSMutableDictionary, you end up doing more work in the long run than if you just make a proper model object.
On a tangential topic, why is your window controller in the NIB? It should be the thing which loads (and owns) the NIB, which of course requires that it exist prior to the NIB being loaded, which means it can't be instantiated in the NIB.