Detecting when NSTextField becomes active - objective-c

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.

Related

How do I explicitly send key-value observing notifications from an NSResponder?

I'm trying to make a NSTouchBar in an SDL application and I need to attach a responder to the NSWindow object (that's the only access SDL gives into the Cocoa windowing system).
https://developer.apple.com/reference/appkit/nstouchbar
If you explicitly adopt the NSTouchBarProvider protocol in an object,
you must also explicitly send the associated key-value observing
notifications within NSTouchBar methods; this lets the system respond
appropriately to changes in the bar.
What does that mean and how do I do it? I see lots of documentation about how to subscribe to the notifications, but not how to send them?
Right now I have:
#interface MyTouchBarResponder : NSResponder <NSTouchBarDelegate>
- (id)init;
- (NSTouchBar *)makeTouchBar;
- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier;
#property(strong, readonly) NSTouchBar *touchBar;
#end
and I'm attaching it to the window with the code from a previous question I asked here: How to create an NSTouchBar from an NSWindow object?
touchBarResponder.nextResponder = window.nextResponder;
window.nextResponder = touchBarResponder;
but my callbacks aren't ever being called (I put exit(0) in them to make it very obvious). When I hack the code directly into the SDL library, things work as expected, but that's not a viable permanent solution.
Thank you.
First, your custom responder should conform to NSTouchBarProvider (in the above, you declare the touchBar property, but not the explicit conformance)
Second, you want to make sure that your custom responder is in the responder chain of the window (whether the first responder or just later in the chain). After adjusting the responder chain with your above code, you want to call -makeFirstResponder: and pass in some view in the window (if you need that view to be first responder) or with the custom responder object. You should then verify that the window's firstResponder is that object.
With these in place, you should get at least one call to touchBar after the window is shown and made key.
To answer the question on key-value observing notifications, that is needed for when you want to change the actual NSTouchBar object being returned from touchBar. In the general case this isn't necessary, since it's unnecessary in the static touch bar case, and even in the dynamic case, you can rely on just setting the defaultItemIdentifiers on the previously created touch bar and it will update. However, should you need to change the touch bar object, you need to ensure that -willChangeValueForKey: and -didChangeValueForKey: are sent for touchBar when you change the return value. This developer documentation on KVO goes into much more detail.

Silence Cocoa error beep

I have a Cocoa application that captures keypresses through a custom view in the view hierarchy. This view implements the keyUp and keyDown methods, and the keypresses are received. Even so, Cocoa still insists on playing the system error sound/ding every time I press a key. Any solutions?
Note: Although I tried to make this view first responder, it didn't work. That may have something to do with it.
If you have unsuccessfully tried to make the view the first responder, it's most likely because NSView returns NO for acceptsFirstResponder. You can have your NSView subclass override acceptsFirstResponder to return YES:
- (BOOL)acceptsFirstResponder {
return YES;
}
That should eliminate the beeps. Alternatively, you could have the NSView subclass override NSResponder's performKeyEquivalent: method to return YES, which should also eliminate the NSBeeps:
- (BOOL)performKeyEquivalent:(NSEvent *)event {
return YES;
}
UPDATE:
Not sure what to suggest. I actually wrote a "Keyboard Cleaner Helper" app that's designed to basically do something similar to what you want. (I used it on my laptop when I wanted to clean the keyboard and didn't the hundreds of key presses to randomly rename files or result in repeated error beeps).
Sample project: http://www.markdouma.com/developer/KeyboardCleanerHelper.zip
Running that app, I can't get it to beep at all (notice calls are logged to Console).

NSArrayController - Observing selectionIndex

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.

Descendant as an parent's delegate

Lets say I want to create my own text view with maximum characters constrain. And I want to do that constrain in level below - in text view.
I think of creating CustomTextView : UITextView where customTextView.delegate would be the same object - customTextView (self.delegate = self). The definition of the class would be CustomTextView : UITextView <UITextVIewDelegate> and I would implement – textView:shouldChangeTextInRange:replacementText: to do the constrain logic in.
But somehow this do not work. Can I get explanation why or what can be wrong and how to achieve my intent?
If you are subclassing UITextView, why would you need to set itself as the delegate? The delegate is only used to notify code outside of the UITextView that something changed in the UITextView. This means that the UITextView is notified of changes to itself first and, using the delegate, you can notify external code (UIViewController, etc.) of what happened. If you are subclassing the UITextView, it should receive those change notifications from the OS.
However, looking through the documentation, I cannot see how you would track the built-in events by subclass alone. Here's an article I found with a Google search: Subclassing a UITextView

Cannot set an NSWindow's position

I have a class that extends NSWindowController and I am trying to position the window it controls. The window displays all of the expected contents and functions correctly, but when I try and position its starting location on the screen in the initWithWindowNibName method, the position does not change. Here is the code:
NSPoint p = NSMakePoint(100, 50);
[[self window] setFrameTopLeftPoint:p];
This seems very straight forward and I'm not sure what the problem is.
Thanks for any ideas.
(Found the problem. I did not have the window wired up to the Class in IB.)
Wevah has the right idea, though I'll try to expand on it a bit.
If you were to try adding this line to your initWithWindowNibName: method:
NSLog(#"window == %#", [self window]);
You would likely see the following output to console:
window == (null)
In other words, the window is still nil, as init* methods are so early on in an object's lifetime that many IBOutlets or user interface items aren't quite "hooked up" yet.
Sending a message to nil is perfectly fine: it's simply ignored. So, basically your attempt to position the window has no effect because it basically equates to [nil doSomething];
The key then is to perform the positioning of the window later on in the controller object's lifetime, where the IBOutlets and other user interface objects are properly hooked up. As Wevah alluded to, one such method where things are properly hooked up is
- (void)awakeFromNib;
or in the case of NSWindowController, the following one as well:
- (void)windowDidLoad;
Hope this helps...
Try putting that code in awakeFromNib.