NSArrayController - Observing selectionIndex - objective-c

When I try setting up observation of this specific key, nothing happens. Here is what I mean.
I have a standard Cocoa application, with an NSTableView, and I figured out how to change the image shown in an image view based on what cell was selected.
Now, I am trying to figure out how to disable/enable buttons by the selected index. What I mean by this, is that I have a button in the window, that is disabled on certain indexes.
SomeClass's init method
-(id)init {
if (self=[super init]) {
[arrayController addObserver:self forKeyPath:#"selectionIndex" options:NSKeyValueObservingOptionNew context:NULL];
}
}
However, when I implement the observeValueForKeyPath: method, the changes are not being picked up as I pick new indexes. As a matter of fact, the outlet shows (null) if I try logging it. However, when I add the observer in AppDelegate, AppDelegate (when specified as the observer) picks up changes.
Is there some reason my generic SomeClass object does not? Should it be done a different way?
NOTE:
I tried subclassing SomeClass as a NSWindow, then making the window's owner SomeClass, and setting up the observer in awakeFromNib, and this works, but seems like a bad way to do it.

The init method happens too early in the process, before the outlets are connected (I think). Putting the code in awakeFromNib will work correctly. This is from Apple's docs in the NSObject class reference:
The nib-loading infrastructure sends an awakeFromNib
message to each object recreated from a nib archive, but only after
all the objects in the archive have been loaded and initialized. When
an object receives an awakeFromNib message, it is guaranteed to have
all its outlet and action connections already established.

Related

Detecting when NSTextField becomes active

To be specific I would like to receive notifications when an NSTextField gets focus, or when the user clicks on it and is about to start editing.
I've tried the textDidBeginEditing: method but this is only fired when the user actually starts to type and I need it when the text field becomes the first responder.
I've tried overriding -(BOOL)becomeFirstResponder but that's only called when the text field first becomes the first responder and not any time after that.
I've tried registering for NSControlDidBeginEditing notifications but never got any.
Is this possible?
Implement the window's delegate method windowWillReturnFieldEditor:toObject:. This tells you that the field editor is switching to a different object. Look to see if this object is the text field.
Alternatively, subclass NSWindow and override makeFirstResponder:. Call super, look to see what responder is becoming first responder and whether the call to super returned YES, and return the result of the call to super.
Just to be clear #matt's answer put me on the right path; I thought I just ought to clarify exactly how you can do this.
Aim
So I wanted to have an NSTextField subclass that would know when it became active (i.e. first responder), and then notify it's delegate.
Problem
It turns out the under the hood of OS X text editing is a messy world and you can't really rely on NSTextField to do it all. Basically when an object that is involved in text editing becomes the first responder, something (the window, the system, the NSApplication) gives it an _NSKeyboardClipView (I think it's called that...) as a subview. In turn the _NSKeyboardClipView has an NSTextView as a subview, and it's the NSTextView that becomes the first responder.
Solution
• Subclass (or extend) NSWindow and override the makeFirstResponder: method.
• Fire a notification using NSNotificationCenter who's object is the responder object that is passed to the makeFirstResponder: method.
• Catch the notification in your NSTextField subclass.
• Here's the horrible bit: you need to check that the notification.object is, a) a subclass of NSView and, b) the notification.object.superview.superview == self (you will have to cast to NSView here because the object will be of type id). e.g:
- (void)didBecomeFirstResponder:(NSNotification *)note
{
if ([note.object isKindOfClass:[NSView class]] && [[(NSView *)note.object superview] superview] == self) {
// You just became the first responder
}
}
It's horrible and tacky/hacky but it does work.

NSWIndowController creating Outlets sequence of nib-loading

I'm having big trouble with a nib containing a NSWindowController (as Files Owner) plus ArrayControllers bound to the Files Owner in the nib.
The subclassed NSWindowController seems ok, but the NSObjectControllers, NSArrayControllers and 'custom' NSObjects within the nib are all set to 0x0 -- after a nib load.
All controllers and objects are properly 'bound' in IB. The NSObject subclss has a initWithCoder. Is there further requirements for inits or awake. I should not that ALL inits are essentially doing nothing, but return super. Thats because all referenced objects are set (bound) within the nib.
It seems it has something to do with the sequence of nib loading. And, I was led to believe that IBOutlets were available BEFORE awakeFromNib, following a [super initWithWindowNibName:name];
Based on the docs,
In Mac OS X v10.5 and later, setting an outlet also generates a
key-value observing (KVO) notification for any registered observers.
These notifications may occur before all inter-object connections are
reestablished and definitely occur before any awakeFromNib methods
of the objects have been called.
Moreover,
If you need to configure the objects in your nib file further at load
time, the most appropriate time to do so is after your nib-loading
call returns. At that point, all of the objects are created,
initialized, and ready for use.
Am I wrong to believe that self = [super initWithWindowNibName:name] is my nib-loading call?
Some of my awakeFromNib are called only when the Window is shown. ie. [myWindowController showWindow: nil]; My window is opened as a sheet that is making a blocking call, thus it is not easy to access those arraycontrollers after a showWindow.
Am I wrong to believe that self = [super initWithWindowNibName:name]
is my nib-loading call?
-[NSWindowController loadWindow] is the method that actually loads the nib. Not that you should ever call it directly; -[NSWindowController window] is the one you should call instead, because the latter invokes -[NSWindowController windowWillLoad] and -[NSWindowController windowDidLoad] as well.
The documentation for NSWindowController states:
Although a window controller can manage a programmatically created
window, it usually manages a window in a nib file. The nib file can
contain other top-level objects, including other windows, but the
window controller’s responsibility is this primary window.
The upshot of this is that nothing in the nib will be loaded until the window in it is loaded.
This is a "gotcha" that used to get me a few times!

Showing an NSPanel on demand - NSPanel not showing?

Hey guys,
I thought this would be an incredibly simple thing to do, but it's proving trickier than I thought.
I want to be able to show an NSPanel that displays a circular progress bar and the name of the thing that's being processed.
So, I made an NSPanel containing those controls in IB, then I created a subclass of NSWindowController. Created outlets for the controls and linked those up.
However, when I try using this code to display the NSPanel, nothing happens:
[[[self controller] msgSubject] setValue:[msg subject]];
[[[self controller] window] setLevel:NSFloatingWindowLevel];
[[self controller] showWindow:self];
[[[self controller] window] makeKeyAndOrderFront:self];
[self controller] is a method that lazily instantiates the NSWindowController subclass. I tried adding the call to makeKeyAndOrderFront: in vain, but the panel still isn't popping up.
I tried debugging and what I found is that when -initWithWindow: is called, the NSWindow that's passed in as an argument has all zeroed out instance variables, leading me to believe there's some sort of IB linking issue going on here.
Any ideas? I'm guessing I missed something really obvious, but I can't for the life of me figure out what it is.
Please post the contents of the [self controller] method so we can begin to see exactly how you're creating and using the NSWindowController subclass. Are you creating it using the initWithWindow: method directly, or is that method (which is the designated initializer) being called indirectly from one of the other init methods? If you are calling it directly, that doesn't really make sense to me as you said you already created the window in nib file itself. If on the other hand, it's being called indirectly by -initWithWindowNibName:, then it would help to see that code.
NSWindowController's are primarily used/set up in 2 different ways. One way is to create an NSWindow programmatically, and then create the NSWindowController subclass and feed that window in as the window the controller will manage. The second, and more frequently used method, is to create a nib file that houses the window, which is what it sounds like you're trying to do. In this method, you generally use the -initWithWindowNibName: initializer. As long as you pass in the proper nib name (generally without the ".nib" part of the filename), and that nib file can be properly found at runtime, and the file's owner in this nib file is set to be the custom NSWindowController subclass, and the window outlet of this subclass is properly hooked up to your window, then you should be all set.
Might want to double-check to make sure that the nib file you want to load is actually in the app bundle. (I've occasionally forgotten to add it to the target so at runtime the nib file couldn't be located and so the -initWithWindow: method would always show a nil parameter).

How does NSViewController avoid bindings memory leak? [have sample app]

I'm trying to implement my own version of NSViewController (for backwards compatibility), and I've hit a problem with bindings: Since bindings retain their target, I have a retain circle whenever I bind through File's owner.
So I thought I'd just explicitly remove my view from its superview and release the top level objects, and that would take care of the bindings, because my controller isn't holding on to the views anymore, so they release me and I can go away. But for some reason, my view controller still doesn't get released. Here's a sample app exhibiting the problem:
http://dl.dropbox.com/u/34351/BindingsLeak.zip
Build it, launch it, and hit Cmd-K ("Create Nib" in "Edit" menu) to load a NIB into the empty window. Hit Cmd-K again to release the first view controller (TestNibOwner) and load a new one. The old view controller never gets dealloced, though.
Remove the "value" binding on the checkbox, and it gets released just fine.
If you set breakpoints at the release/retain/autorelease overrides, you see that _NSBindingInfo retains the TestNibOwner, but never releases it in the leaking case.
Anyone know how to fix this?
Doing a little investigation with class-dump and friends, it looks like Apple has a private class called NSAutounbinder that takes care of this dirty work for classes such as NSViewController and NSWindowController. Can't really tell how it works or how to replicate it though.
So, I can't really answer your question on how to prevent the retain cycle from happening for arbitrary bindings in a loaded nib, but perhaps it's some consolation to know that Apple is cheating, and you're not missing anything obvious. :-)
One thing I've done for the same problem is to create a proxy NSObjectController inside my nib. My NSViewController-like class has a pointer to this proxy and all bindings are bound through it. When I want to cleanup the view controller, I then do [selfProxy setContent:nil] on the object controller and release the view controller. In this instance the NSObjectController proxy acts as the auto-unbinder in this case.
It's more manual and you can't just release the view by itself, but it does solve the retain problem.
I'd suggest you do this:
-(void) releaseTopLevelObjects
{
// Unbind the object controller's content by setting it to nil.
[selfProxy setContent:nil];
NSLog( #"topLevelObjects = %#", topLevelObjects );
[topLevelObjects release];
topLevelObjects = nil;
}
In your nib, bindings would happen through a path like:
selfProxy.content.representedObject.fooValue
When you remove your view from its superview, are you also sending it another -release message? It was created by unarchiving from the nib, right?

3 notifications instead of one

I'm developing simple MVC app in Cocoa/Objective-C. I have a strange issue (or misunderstanding) with notifications and KVO.
I have AppController object in MainMenu.xib, hence I implement awakeFromNib method where I register for NSImageView changing its image property. I add self as an observer in the following way:
// options:3 equals to new/old passed values in changeDictionary
[backgroundImageView addObserver:self
forKeyPath:#"image"
options:3
context:NULL];
The backgroundImageView is an IBOutlet in AppController connected to NSImageView.
In standard observeValueForKeyPath:ofObject:change:context method I just log the received notification.
Problem is - when i change the image value of NSImageView I get 3 notifications instead of one. Can you help me with this? Maybe I'm overlooking something in options or in generally registering observer?
UPDATE: backgroundImageView is the instance of BackgroundImageView class which is sublcass of NSImageView. I subclassed the latter one for handling drag and drop operations as drag destination. When performDragOperation: is called (the last 'state' of the dragging) it changes the value for image property with setImage between willChangeValueForKey and didChangeValueForKey.
… it changes the value for image property with setImage between willChangeValueForKey and didChangeValueForKey.
When you send an accessor message, you get KVO notifications for free with it. You should remove the {will,did}ChangeValueForKey: messages, because they're the cause of at least one of the extraneous change notifications.
Is your AppController the File's Owner of two other nibs? If so, it'll receive an awakeFromNib message for each of those, too. MainMenu plus two makes three awakeFromNib messages, which means you'll add yourself as an observer three times.
There does not seem to be any obvious problem with setting of the observer.
Have a look at how you update the image that you observe, maybe it's being modified 3 times?