Observing NSWindow properties breaks when a window's delegate is set - cocoa

I'm observing the following notifications for a window:
NSWindowDidMoveNotification
NSWindowDidResizeNotification
NSWindowDidChangeScreenNotification
I register as an observer in my NSWindowController subclass using the following code:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(persistedWindowPropertiesDidChange:)
name:<Notification Name>
object:self.window];
I observed behavior that I haven't experienced before and was wondering why it was occurring.
If the window's delegate property is set to nil, everything works as expected (notifications are observed). However, if the window's delegate is set to an object, passing self.window for the object parameter of -addObserver:selector:name:object: causes the selector to never be called. If nil is passed for the object parameter, the selector is called for all windows (expected behavior). Why does setting the delegate of the window break the filtering by object behavior of the notification center? This is occurring on 10.11.6.
Thanks for any insight!

Related

How to tell which NSTextField was edited?

My NSDocument subclass adopts NSTextFieldDelegate. The document window contains several NSTextField instances (all setup as outlets of the document class). When the user edits a text field, I want my document to be notified. But all the methods in the NSTextFieldDelegate protocol are inherited from NSTextViewDelegate and hence pass NSText* instances in their parameters, NOT NSTextField instances. The same applies to the notification:
- (void) controlTextDidChange:(NSNotification*) notification
So, How do I find out which of the many NSTextField instances is being edited?
NOTE
I need to register undos properly, using the document's undo manager. I tried implementing
- (NSUndoManager *)undoManagerForTextView:(NSTextView *)aTextView
but this seems to only work with NSTextViews, NOT NSTextFields.
You can get a reference to your NSTextField as [notification object]. From the documentation for NSControlTextDidChangeNotification:
The notification object is the NSControl object posting the
notification.
The actual control subclass will be your NSTextField.

NSWindow tracking

I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?
windowWillLoad, maybe?
I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.
How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.
see : Apple Documentation
Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.
You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.
Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.
If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.
If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:
#interface MyWindowController ()
#property (nonatomic) BOOL windowIsVisible;
#end
and I implemented the accessors like this:
- (BOOL)windowIsVisible { return self.window.visible; }
- (void)setWindowIsVisible:(BOOL)windowIsVisible {
NSLog(#"window %# became %s", self.window, windowIsVisible ? "visible" : "hidden");
}
and in awakeFromNib, I bind the window's visible binding to the property like this:
- (void)awakeFromNib {
[super awakeFromNib];
[self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(#selector(windowIsVisible)) options:nil];
}
When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.
Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.

Receive window notifications

I have an NSWindow set up in Interface Builder. I have set the class of File's Owner to my NSWindowController and linked the window property of the controller to my NSWindow.
My controller implements NSWindowDelegate.
Now, in my controller, I have added the following:
- (void)windowDidLoad
{
[super windowDidLoad];
[self.window setDelegate:self];
}
- (void)windowDidBecomeMain:(NSNotification *)notification
{
NSLog(#"Did become main.");
}
Still, -windowDidBecomeMain: isn't called. Does anyone know why this is?
EDIT:
Trying to show a window from AppDelegate on launch. The main nib (declared in Info.plist) contains a menu item only which is linked to the AppDelegate. In the application delegate, I show an icon on the status bar and when this icon is clicked, I display the menu from the main nib.
In the application delegate, I also want to display a window which should have a window controller assigned to take care of the logic.
I believe that when this works, I will receive my window notifications.
Now, the following code doesn't show the window and I can't figure out why.
DemoWindowController *dwc = [[DemoWindowController alloc] initWithWindowNibName:#"DemoWindowController"];
[dwc showWindow:self];
Note that self is the application delegate.
I suspect your problem is due to the fact that your window controller is not actually the object that is the nibs file owner.
When you change the class in interface builder you are telling it what outlets and actions are available (which is why you are able to drag to the window outlet) but you are still responsible for passing in this object yourself.
In the case of a non-document based application, you will have a main method which calls NSApplicationMain. What this does is basically look up and load the window nib that is specified in your info.plist file and pass the current NSApplication instance to this nib as the files owner (so even though you changed the class type to NSWindowController, the object being passed in is actually of type NSApplication).
The easiest way to fix your problem is to get rid of your window controller for now (as it isn't actually doing anything yet).
You should implement the -windowDidBecomeMain: method in your app delegate. Then Ctrl+drag from your window to your appDelegate to set it as the delegate of the window to get your notifications.
Update
To answer your question regarding the WindowController beware of the following two issues:
You are creating your window controller variable (dwc) in your applicationDidFinishLaunching: method. This is released the moment you leave the method taking your window with it. Create an instance variable to hold onto the window controller instead.
Ensure that your second window nib has its file owner set to NSWindowController (or your window controller type) and that its window outlet is connected to the window in the nib file.
Your window should now display.

Check if NSTextView has been edited

Is there a way to get notified when a NSTextView gets modified?. In a NSTextField I just set the target for the default sent action and works perfectly, but I don't see any sent actions on a NSTextView.
NSTextView inherits from NSText, which conforms to the NSTextDelegate protocol. Look it up in the docs. The method you are looking for is: - (void)textDidChange:(NSNotification *)aNotification which you can either implement in your TextView's delegate or get by registering for a "NSTextDidChangeNotification" notification.
Subclass NSTextField and override the textDidChange method that it has.
The delegate will tell you when it will start editing and when it will finish editing. But the control itself gets the textDidChange method called on itself.
Subclass it and override the method
- (void)textDidChange:(NSNotification *)notification;
then you could set a flag that you can access externally.

Quit app when NSWindow closes

How correctly to quit the Mac OS X app, when the main (the only one) closes?
I know there a method - (void)windowWillClose:(NSNotification *)notification in NSWindowDelegate. But it isn't quite suitable in my case, because it is called before NSWindow closes.
You cannot have windowDidClose event since the notification that accompanies it would be holding an invalid object (the window is likely to have been deallocated on close). To achieve what you need, make your class the delegate of the Application, and implement the following method:
- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) theApplication;
From that method, return YES.
If your controller object has an instance in the MainMenu.nib, just make a connection from File's Owner (which means Application Object in the MainMenu.nob file). Control-Drag from File's Owner to your object, and connect the delegate outlet.
Or in source code, put something like this in your controller object's init method:
[NSApp setDelegate: self];

Resources