How to define a class that sends an action like NSButton? - cocoa

In Cocoa, how do you define a class that sends an action? I want to be able to connect the action to the selector of another object in IB in the style of NSButton. I would prefer not to subclass NSControl if possible.

Give it a property (informal is fine) holding an id, for the target. I'm not sure whether this should retain or not; I'd say no, since the target will normally be the controller that owns the window that (indirectly) owns the view.
Give it a property (informal in fine) holding a SEL, for the action.
Respond to mouseUp: and keyDown: (checking that the key in question is the space bar or return or enter) by sending yourself an accessibilityPerformAction: message, passing NSAccessibilityPressAction.
Respond to the accessibilityPerformAction: message by either sending your action message to your target (NSAccessibilityPressAction) or calling up to super (other), as described in the documentation for that method.
You should also implement the rest of the NSAccessibility protocol while you're at it. Test that work with a mix of the Accessibility Inspector and VoiceOver.

Related

Why should -[NSTextInputClient doCommandBySelector:] not pass the event up the responder chain?

A text view should not forward uninvokable commands up the responder chain, the docs say for -[NSTextInputClient doCommandBySelector:]:
If aSelector cannot be invoked, then doCommandBySelector: should not pass this message up the responder chain. NSResponder also implements this method, and it does forward uninvokable commands up the responder chain, but a text view should not. A text view implementing the NSTextInputClient protocol inherits from NSView, which inherits from NSResponder, so your implementation of this method will override the one in NSResponder. It should not call super.
The last sentence does not clarify but only rephrase how things are set up if my text understanding does not fail me.
So there basically just is a prescription: "a text view should not". Period.
But why?
I can fathom a case where you want a text view not react to any/all NSResponder methods but delegate these up to its view controller, for example. Would this cause trouble? Is this just advice to keep text view behavior consistent across macOS apps?
From The Key-Input Message Sequence:
If the first responder is a text view, the key event enters the text system. The key window sends the text view a keyDown: message with the event as its argument. The keyDown: method passes the event to handleEvent:, which sends the character input to the input context for key binding and interpretation. In response, the input context sends either insertText:replacementRange:, setMarkedText:selectedRange:replacementRange:, or doCommandBySelector: to the text view.
It wouldn't be correct if the text view handles the key event and the scroll view or some other view receives the doCommandBySelector: message. You are not allowed to send doCommandBySelector: to super but you are allowed to send the selector to a delegate.

NSControl with multiple actions

I'm trying to create a complex custom NSControl that must be able to send more than one message.
For example, on mouse over in must send an action, and on mouse drag in must send another action.
I can't understand how to wire a target to a control and make the control sends whatever message to the target.
In my opinion i have to follow these steps:
Instantiate the NSControl i.e. myControl
set the Target action for myControl for every actions (I don't know how to do that!)
The myControl instance will send action with [NSApp sendAction: [self action] to: [self target] from: self]
Can you help me on step 2? and confirm my steps?
You need Delegation pattern. Standard Cocoa controls send at most one action, and use delegation for anything additional. IB does not support setting more than one action, so you can't solve step 2.
If delegate is an outlet, you can set it right from IB whenever the delegate is file's owner or also instantiated in this nib, like you would do that for e.g. NSWindow or NSTableView.

How can I receive actions listed in here? (Cocoa)

I'm trying to connection the actions to specific object in IB tree. When I drag the action from the circle on the right side of each action name, no object accepts the line except NSScroller instance.
NSWIndow, NSResponder or any other kind of objects doesn't accept the dragged action. What's required to accept the action listed here?
These are action messages that you can send to the selected object (i.e., that it can receive and respond to). You can drag from any of those circles to any object (most probably a control, most probably a button) that has a target and an action; doing so will set the control's target property to this object and the control's action property to the action you connected it to.
I decided to use a delegate method.
- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector
Here's a usage: http://www.cocoabuilder.com/archive/cocoa/48070-adding-carriage-return-behavior-to-nstextview.html

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.

How to get notifications of NSView isHidden changes?

I am building a Cocoa desktop application. I want to know when a NSView's isHidden status has changed. So far using target/action doesn't help, and I can't find anything in NSNotification for this task. I would like to avoid overriding the setHidden method, because then I'll have to override all the NSView derived class that I am using.
UPDATE: I ended up using KVO. The path for "isHidden" is "hidden", probably because the setter is "setHidden".
You could use Key-Value Observing to observe the isHidden property of the NSView(s). When you receive a change notification from one of these views, you can check if it or one of its superviews is hidden with -isHiddenOrHasHiddenAncestor.
A word of warning: getting Key-Value Observing right is slightly tricky. I would highly recommend reading this post by Michael Ash, or using the -[NSObject gtm_addObserver:forKeyPath:selector:userInfo:options] method from the NSObject+KeyValueObserving category from the Google Toolbox for Mac.
More generally, one can override viewWillMoveToWindow: or the other related methods in NSView to tell when a view will actually be showing (i.e. it's window is in the window display list AND the view is not hidden). Thus the dependency on KVO for the 'hidden' key used above is removed, which only works if setIsHidden has been called on that view. In the override, 'window' (or [self window]) will indicate whether the view is being put into a visible view hierarchy (window is non-nil) or being taken out of it (window is nil).
I use it for example to start/stop a timer to update a control from online data periodically - when I only want to update while the control is visible.
Could you override the setter method for the hidden property so that it will trigger some custom notification within your application?

Resources