Should my custom class be set as my referencing outlet? - cocoa

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

Related

How to manage windows in cocoa

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];
}

How can I directly respond to NSTableView edits while still using NSArrayController?

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.

How do I programmatically change a custom view object's member variables?

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.)

NSWindowController subclass initialization from a nib doesn't use -initWithCoder:?

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.

Accessing the controller in a Cocoa application

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

Resources