validateMenuItem on NSViewController - macos

Is it possible to have validateMenuItem work with NSViewControllers? The docs say NSViewController is a subclass of NSResponder so I would think it would participate in the responder chain, but I have never been able to get it to work. I can make it work with NSView, no problem.
The particular use case I have is a menu item in the MainMenu.xib, connected to First Responder. The selector from my view controller subclass shows up in the First Responder pop up list of selectors in MainMenu, so I would think it would work, but it doesn't. The validateMenuItem method of my subclass is never called. (The one in the app delegate is called, but not for the item which is connected to First Responder.)

Related

Non-NSView NSResponder to handle NSDocument responder actions

My NSDocument subclass (MyDocument) is quite large and I was thinking about making a MyDocumentResponder (subclass of NSResponder) to handle document responder events in order to clean up the code. This seems better to me than a category on MyDocument.
MyDocumentResponder would be instantiated in the MyDocument nib and would have a property that hooks it to its owning MyDocument.
Within MyDocumentResponder, I can implement acceptsFirstResponder, but how do I correctly hook it into the responder chain (and remove it if the document loses its front-most status?
Should I hook it in by adding becomeFirstResponder and resignFirstResponder to MyDocument? In this case, should it go in front of or behind MyDocument?

adding delegate to responder chain stops scrolling from working

For a number of reasons, I have added my class that implements NSOutlineViewDelegate protocol to the responder chain:
[myOutlineView setNextResponder:self];
This stops my outline view from scrolling. Take the call out - scrolling works fine, put it back - scrolling stops. If I use the up and down arrows to move the selection through the view it scrolls to show the selected row OK, but gesture scrolling doesn't do anything.
The delegate contains quite a few methods for supporting drag and drop, and ibaction methods for supporting context menus, but I can't think what is in there that would interfere with scrolling (I am using a macbook air with gesture scrolling). Anyone got any ideas what is causing the interference? or any ideas how to diagnose?
I should add that I made the delegate class a subclass of NSResponder.
So the answer is, when adding a delegate into the responder chain, you must also add to the delegate the responder that used to be in its place - otherwise the chain gets broken and the events don't get handled, so it goes:
NSResponder *nextResponder = myOutlineView.nextResponder;
[myOutlineView setNextResponder:self];
[self setNextResponder:nextResponder];
With the responder chain restored, my outline view now scrolls again. Hooray

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.

When an NSWindow object has a delegate that is a NSWindow subclass, who is responsible to act on received events?

So I'm building a program that features the use of the IKImageBrowserView component as a subview in an NSWindow. As a side note, I have a controller object called ImageBrowserController which subclasses NSWindow and is set as the delegate of the NSWindow object of my app.
I have sent IKImageBrowserView the message setCanControlQuickLookPanel:YES to enable it to automatically use the QuickLook functionality to preview image files when the IKImageBrowserView is a first responder to receive key events. Then it took me a while to figure out how to make the IKImageBrowserView a first responder which I finally got working by overriding acceptsFirstResponder inside my ImageBrowserController.
Now I understand that as the delegate to the NSWindow, ImageBrowserController has a place in the responder chain after the event gets triggered on NSWindow. And I understand that as a subview of NSWindow, IKImageBrowserView is in line to be passed events for event handling. What I don't get is where the connection is between the ImageBrowserController being a first responder and the event somehow making it to the IKImageBrowserView. I didn't set NSWindow or IKImageBrowserView as first responders explicitly. So why isn't it necessary for me to implement event handling inside my ImageBrowserController?
EDIT: So after reading the accepted answer and going back to my code I tried removing the acceptsFirstResponder override in my ImageBrowserController and the QuickLook functionality still triggered just like the accepted answer said it would. Commenting out the setCanControlQuickLookPanel:YES made the app beep at me when I tried to invoke QuickLook functionality via the spacebar. I'm getting the feeling that my troubles were caused by user error of XCode in hitting the RUN button instead of the BUILD button after making changes to my code (sigh).
Some of what you are saying regarding the interactions between your objects does not make sense, and it is hard to address your stated question without some background.
As you say, your window delegate has a place at the end of the responder chain, after the window itself. The key point I think you are missing is that GUI elements, such as your IKImageBrowserView, will be at the beginning of the chain, and any one of them in a given window could be the current firstResponder.
When your application gets an event, it passes it off to the key window (which is just the window which currently accepts "key" (i.e., "keystroke") events). That window begins by asking its firstResponder to handle the event. If that object refuses, it passes the event to its own nextResponder, usually its superview, which either handles it or passes it on, until the event has either been handled or passed all the way up to the window object itself. Only then will the window (if it does not handle the event itself) ask its delegate to handle the event.
This means that the connection between the window delegate and the IKImageBrowserView is only through the Responder Chain, and its nature is simply that if the view declines to handle any given event, the delegate may eventually be asked to handle it, if no other object in between them handles it first.
Your window delegate does not need to be a firstResponder. Nor does overriding acceptsFirstResponder on the window delegate have any effect on one of the window's subviews.*
Your window delegate also does not need to (and, indeed should not) be a subclass of NSWindow. All it needs is to be a subclass of NSObject which implements whatever methods from the NSWindowDelegate Protocol you are interested in, and methods to handle any events you might want to catch if they are not handled by other objects.
So, the answer to your explicit question at the end is (and I do not mean this sarcastically): you only need to implement event handling in your window delegate if you want it to handle events itself.
*: IKImageBrowserView already responds YES to acceptsFirstResponder. If there are no other subviews in your window, it will automatically be the firstResponder when your application starts. You can set this initialFirstResponder explicitly in Interface Builder by connecting that outlet on the window to whatever object you want.

Accessing IBOutlet from other classes

I have a document based cocoa application with an item in the application menu hooked up to an IBAction. Clicking the item needs to perform a task that uses an IBOutlet in the main nib file which is using another class, MyDocument. Creating 2 objects of the same class, one in each nib seems to not be working. How can I access the outlet?
Actions for menu items are often sent to the first responder so that whatever is currently selected can act on it.
It sounds like this action is something that works on the current document, then it should be implemented by the document. In this case have the menu send it's action to the first responder and then put the action method in the MyDocument class.
If the action you are trying to send is a custom one: in the Main Menu nib select the First Responder item, add your method name, then connect the menu item's selector to the action.
Read the Responders section of the Cocoa Event-Handling Guide for more info.
To summarize the above, in your NIB/XIB file, in interface builder make the connection to the First Responder object, not to Files Owner or anything else. You'll still be offered a lit of actions across potential first responders.
Cocoa then takes that selector and looks for it, starting with the NSView (if any) that's currently the first responder, then with the NSDocument that's currently in use, then with it's window controller etc etc all the way up to the Application delegate. The first object it checks that actually implements that method, it will use that object (after validating it with that same object).
So:
#interface MyDocumentTypeA : NSDocument {
}
-(void)myMenuAction:(id)sender;
-
#interface MyDocumntTypeB : NSDocument {
}
// -myMenuAction: not implemented here
-
#interface MyApplicationDelegate ... {
}
-(void)myMenuAction:(id)sender;
-
In Interface builder (or even programmatically), if you've linked the "action" of the menu item to a selector named "myMenuAction:" on the First Responder (which equates to not specifying a target when done programmatically), for the above two document subclasses the following will happen.
For MyDocumentTypeA, when the user selects that menu item, MyDocumentTypeA's -myMenuAction: will be invoked. Since MyDocumentTypeB does not implement this action, Cocoa will continue to look up the responder chain until it gets to your application delegate, which does implement it, so it will be invoked here instead.
If Cocoa finds no objects in the responder chain that implement the method, the menu item remains disabled.
There is a way how to do this, I've posted the answer in a similar thread: Access IBOutlet from other class (ObjC)

Resources