How to change NSButton's title when cursor is on it - macos

I'm beginner of Cocoa Programming.
How can I change NSButton's title when cursor is on the button? (without clicking).

If you look at the class hierarchy for NSButton you'll see that it derives from NSResponder which is the class that handles mouse events.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nsbutton_Class/Reference/Reference.html
Create a subclass of NSButton and override the following messages to set the title to what you want:
- (void)mouseEntered:(NSEvent *)theEvent
- (void)mouseExited:(NSEvent *)theEvent
Add this to your initializer (Either awakeFromNib or your init message, depending on your usage):
[self addTrackingRect:[self bounds] owner:self userData:NULL assumeInside:YES];
Note that even when the mouse has not actually entered the tracking area the first time the title will display the exited message. You may want to add some state to your class to if you want a third title set before it enters the tracking area the first time.
EDIT: Maybe this will help.
Here is the header file "MyButton.h":
#import <Cocoa/Cocoa.h>
#interface MTButton : NSButton {
NSTrackingRectTag myTrackingRectTag;
}
#end
Pretty standard stuff.
Here is my source file.
#import "myButton.h"
#implementation MTButton
- (void) awakeFromNib
{
[self setTitle:#"Initial"];
myTrackingRectTag = [self addTrackingRect:[self bounds]
owner:self
userData:NULL
assumeInside:YES];
}
- (void) dealloc
{
[super dealloc];
[self removeTrackingRect:myTrackingRectTag];
}
- (void)mouseEntered:(NSEvent *)theEvent
{
[super mouseEntered:theEvent];
[self setTitle:#"Entered"];
}
- (void)mouseExited:(NSEvent *)theEvent
{
[super mouseExited:theEvent];
[self setTitle:#"Exited"];
}
#end

Related

Custom NSCursor for ToolbarItem

After reading several questions on the subject here, I have successfully used "custom" cursors for textfields (labels), window contentViews, and buttons using a couple of approaches but I can't make either work for a toolbarItem. Here's the current code:
CustomToolbarItem.h:
#import <Cocoa/Cocoa.h>
#interface CustomToolbarItem : NSToolbarItem
#end
CustomToolbarItem.m:
#import "CustomToolbarItem.h"
#implementation CustomToolbarItem
- (void) awakeFromNib {
// setup trackingarea for cursor control (mouseEntered/mouseExited)
NSTrackingArea *trackingArea= [[NSTrackingArea alloc]
initWithRect:self.view.bounds
options: (NSTrackingInVisibleRect |
NSTrackingMouseEnteredAndExited |
NSTrackingActiveAlways)
owner:self
userInfo:nil];
[self.view addTrackingArea:trackingArea];
}
- (void)mouseEntered:(NSEvent *)theEvent {
[[NSCursor pointingHandCursor] push];
}
- (void)mouseExited:(NSEvent *)theEvent {
[[NSCursor pointingHandCursor] pop];
}
#end
I've also tried setting owner: to self.view, but no joy.
The alternate approach that works for textfields, buttons, etc. that support the drawRect method:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
- (void)resetCursorRects {
[self addCursorRect:self.bounds
cursor:NSCursor.pointingHandCursor];
}
I can't directly modify/address the ToolbarItem's view in XCode's interface builder so I'm stumbling in the dark here.
Any help would be greatly appreciated.
Thanks

How do you make an NSTextField in a custom View be the first responder?

It's my understanding that making the textfield the first responder means that you can enter text into the textfield without having to click on the textfield first.
I made a simple app to demonstrate the problem. Here are the files:
//
// AppDelegate.m
// MyFirstResponderApp
//
#import "AppDelegate.h"
#import "MyViewController.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property(strong) MyViewController* viewCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
...
//
// MyViewController.h
// MyFirstResponderApp
//
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSViewController
#property IBOutlet NSTextField* textField;
#end
...
//
// MyViewController.m
// MyFirstResponderApp
//
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do view setup here.
[[self textField] becomeFirstResponder]; //I tried here...
}
//And overriding initWithNibName:bundle: and setting it here:
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
[[self textField] becomeFirstResponder];
}
return self;
}
#end
The File's Owner of MyView.xib is MyViewController, and here are the File's Owner outlets:
Response to answer:
1) This works for me:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
if ([[myViewCtrl textField] acceptsFirstResponder]) {
[[[myViewCtrl textField] window] makeFirstResponder:[myViewCtrl textField]];
}
}
3) Yes, I am trying to do the first responder dance for the initial showing of the window, but this doesn't work for me:
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
/*
if ([myViewCtrl acceptsFirstResponder]) {
[[[myViewCtrl textField] window] makeFirstResponder:[myViewCtrl textField]];
}
*/
[[self window] setInitialFirstResponder:[myViewCtrl textField]];
}
The window displays fine, but I have to click on the textfield in order to enter text. If I use makeFirstResponder: instead, then the textfield operates like I want:
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
/*
if ([[myViewCtrl textField] acceptsFirstResponder]) {
[[[myViewCtrl textField] window] makeFirstResponder:[myViewCtrl textField]];
}
*/
[[self window] makeFirstResponder:[myViewCtrl textField]];
}
I found that suggestion at Apple Developer's Event Handling Basics, section Setting the First Responder.
Response to comment under answer:
In my app, if I select MainMenu.xib then uncheck Visible at Launch, the code below successfully makes the textfield in the View the First Responder:
//
// AppDelegate.m
// MyFirstResponderApp
//
#import "AppDelegate.h"
#import "MyViewController.h"
#interface AppDelegate ()
#property(weak)IBOutlet NSWindow* window;
#property(strong) MyViewController* viewCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
[[self window] setInitialFirstResponder:[myViewCtrl textField] ];
[self.window makeKeyAndOrderFront:self];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
If I create a WindowController (along with a .xib, and delete the window in MainMenu.xib), then it makes the most sense to me to organize the configuration/initialization like this:
//
// AppDelegate.m
// MyFirstResponderApp
//
#import "AppDelegate.h"
#import "MyViewController.h"
#import "MyWindowController.h"
#interface AppDelegate ()
#property(strong) MyWindowController* windowCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self setWindowCtrl:[[MyWindowController alloc]
initWithWindowNibName:#"MyWindow"]];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
Then setup the View by overriding initWithWindowNibNamein the WindowController:
//
// MyWindowController.h
// MyFirstResponderApp
//
#import <Cocoa/Cocoa.h>
#interface MyWindowController : NSWindowController
-(instancetype)initWithWindowNibName:(NSString *)windowNibName;
#end
...
//
// MyWindowController.m
// MyFirstResponderApp
//
#import "MyWindowController.h"
#import "MyViewController.h"
#interface MyWindowController ()
#property(strong) MyViewController* viewCtrl;
#end
#implementation MyWindowController
-(id)initWithWindowNibName:(NSString *)windowNibName {
if (self = [super initWithWindowNibName:windowNibName]) {
MyViewController* myViewCtrl = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self setViewCtrl:myViewCtrl];
[[self window] setContentSize:[[myViewCtrl view] bounds].size];
[[self window] setContentView:[myViewCtrl view]];
[[self window] setInitialFirstResponder:[myViewCtrl textField] ];
[self showWindow:self]; //I had to uncheck 'Visible at Launch' for the WindowController's window in the Attributes inspector
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
#end
...
//
// MyViewController.h
// MyFirstResponderApp
//
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSViewController
#property(weak) IBOutlet NSTextField* textField;
#end
A couple of things:
You should never call -becomeFirstResponder yourself (except to call through to super in an override). It says right in the docs:
Never invoke this method directly.
The method is the system informing an object that it's about to become the first responder and giving it a chance to refuse. It doesn't actually make the object the first responder.
The way to make a view the first responder is to first check that it will accept that status and then tell the window to make it the first responder:
if ([self.textField acceptsFirstResponder])
[self.textField.window makeFirstResponder:self.textField];
You are currently trying to make the text field the first responder during initialization of the containing view's view controller. This is too early. The view can't be in a window at this point. So, it can't be a window's first responder, yet. You should do this after the view has been added to a window.
That job rightfully belongs in a window controller, not a view controller. After all, it's only the window controller which has the overview necessary to decide which of its (possibly many) views should be the first responder. You aren't using a separate window controller in this simple app, so the responsibility would fall to the AppDelegate class. (It's effectively acting as the window's controller, given the code you posted.)
If this is happening before the window is shown, you should consider setting the window's initialFirstResponder rather than forcing a view to be the first responder immediately. The window will make its initialFirstResponder the first responder when it is first shown.
It's better to reserve -makeFirstResponder: for programmatically changing the first responder after the window has been shown.
setting First Responder immediately during initialization can be ignored. especially for a UI component attatched to View Controller other than Window Controller.
There might be a delay between viewWillAppear() and viewDidAppear().
it can be done with window's initialFirstResponder property, or makeFirstResponder() medthod like...
window?.initialFirstResponder = yourVC.yourTextField
//OR
window?.makeFirstResponder(yourVC.yourTextField)
(Sorry, My brain and hands have deprecated Objective-C )
FYI, the window property can be accessed various ways in some places.
in your custom VC, it would be:
self.yourTextField.window?
//OR
NSApplication.sharedApplication().mainWindow?
In your custom WindowController:
self.window?
//or
NSApplication.sharedApplication().mainWindow?
In your Appdelegate.swif :
window? //if you use that default property in the template.
//if you have your custom Window Controller instance 'mainWindowController' :
self.mainWindowController.window?
//if you have a custom VC loaded to custom WC, well, :
self.mainWindowController.yourVC.window?
//OR just use
NSApplication.sharedApplication().mainWindow?
but like I said, it can be ignored during luanching if you set it immediately. So you set it after delay:
window?.performSelector(#selector(window?.makeFirstResponder(_:)),
withObject: .yourTextField,
afterDelay:0.5)
Depending on where you put this, the dot notations of 'window?' and '.yourTextField' parts should be different.

Black sceen trying to put a .Xib after Splash

I'm programming a App for iOS 8. I've a problem because i don't want use storyboard, I want to put a .xib file at the first time. Muy problem is that always, after splash be a black screen.
The Class who I want to put after Splash is LoginViewController
At general options, the "Main interface" is white (empty) (if put the name of the class, i've NSException (NSUknownException)).
In the .xib file i'have the files owner connected, and at the right side of the screen i've the name of the class at "Custom Class" (LoginViewController).
My appDelegate.h is: (I try with " #property (strong, nonatomic) LoginViewController *viewController;" too)
#import <UIKit/UIKit.h>
#import "LoginViewController.h"
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UIViewController *viewController;
#end
My appDelegate.m is: (i try a lot of variants)
#import "AppDelegate.h"
#import "LoginViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.viewController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
navController.navigationBarHidden = YES;
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
The LoginViewController.h:
#import <UIKit/UIKit.h>
#interface LoginViewController : UIViewController
{
IBOutlet UILabel *labelPrueba;
IBOutlet UIButton *botonPrueba;
IBOutlet UIButton *boton2prueba;
IBOutlet UILabel *label2Prueba;
IBOutlet UILabel *dsfd;
IBOutlet UIButton *dfdf;
}
#end
The LoginViewController.m:
#import "LoginViewController.h"
#interface LoginViewController ()
#end
#implementation LoginViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
label2Prueba.text = #"laaaaaaaaaaaaa";
NSLog(#"Entra en viewDidLoad");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
Someone can help me please. I'm going crazyyyyy.
Thanks a lot.
The solution was put this windows like init frame:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// That was the solution
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// That was the solution
self.viewController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
navController.navigationBarHidden = YES;
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
return YES;
}
Thanks

NSViewController mouseDown: not called

I have a NSWindowController where I'm adding my corresponding ViewControllers. I would like to handle touch events in one of the ViewController. My class is
Window.h
#interface WindowController : NSWindowController<NSToolbarDelegate>
#property (nonatomic, strong) NSViewController *currentViewController;
#property (assign) IBOutlet NSView *targetView;
#end
Window.m
-(void)addViewController
{
NSViewController *currentController = [[currentControllerClass alloc]initWithNibName:controllerIdentifier bundle:nil];
self.currentViewController = currentController;
[self.targetView addSubview:self.currentViewController.view];
[self.currentViewController.view setFrame: [self.targetView bounds]];
}
-(void) awakeFromNib{
[super awakeFromNib];
[self.window setIgnoresMouseEvents:NO];
[self setToolbarToPracticeView];
}
-(void)mouseDown:(NSEvent *)theEvent
{
NSLog(#"Window > Mouse down");
}
I'm able to print the mousedown event here. Now I have a viewController, which is
ViewController.h
#interface ViewController : NSViewController
#end
ViewController.m
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
-(void)awakeFromNib
{
[self.view setAcceptsTouchEvents:YES];
}
-(BOOL)acceptsFirstResponder
{
return YES;
}
-(void)mouseDown:(NSEvent *)theEvent
{
NSLog(#"ViewController > mouse down");
}
I would like to get the control of the mouseDown in the ViewController. Am I missing something here?
Thanks.
Create a Custom NSView and then delegate a mouseDown method in it's protocol. Import and use the customView in the ViewController and delegate its methods. When you click on the view a mouseDown event triggers.
Not sure of my answer, but if you don't call [super mouseDown:theEvent] in the Window.m (which should be called WindowController.m by the way), you are breaking the responder chain. Hence your view controller can't see it. Am I right?

NSTextField not noticing lost focus when pressing Tab?

I can't seem to find a way to get notified when an NSTextField loses focus by pressing the Tab key. I get a nice textDidEndEditing when clicking another control or when pressing Enter, but not if I change the focus by pressing the Tab key.
Also tried to yank KeyDown and doCommandBySelector for this purpose but I got nowhere.
Any ideas?
Thanks in advance
Edit:
Forgot to mention, but I tried resignFirstResponder too. This is the code I tried:
- (BOOL)resignFirstResponder
{
NSRunAlertPanel(#"", #"Lost Focus",#"OK", nil, nil);
return [super resignFirstResponder];
}
- (BOOL)becomeFirstResponder
{
NSRunAlertPanel(#"", #"Got focus",#"OK", nil, nil);
return [super becomeFirstResponder];
}
Strangely, what happens here is that when getting focus, both becomeFirstResponder and resignFirstResponder are called one after the other. But when changing focus away from the control, neither are.
"I get a nice textDidEndEditing when
clicking another control or when
pressing Enter, but not if I change
the focus by pressing the Tab key."
As of April 2011, with OS X 10.6 libs, I'm using:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
...to listen for NSTextField losing focus, and it's working correctly. Is this possible in your situation? Is it something that used to be broken, but is now fixed by Apple?
If so, it's much less code :).
Ok, I've found a way to do it: use a window delegate to make the window return a custom field editor. This field editor keeps track of the last TextField that's been activated and calls its textDidEndEditting method when losing firstResponder itself. Here's an example of how to do it:
#import <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#interface MyTextField : NSTextField
- (BOOL)resignFirstResponder;
- (void)textDidEndEditing:(NSNotification *)notification;
#end
#interface MyFieldEditor : NSTextView
{
MyTextField * lastBox;
}
-(void) setLastEditBox:(MyTextField*) box;
#end
#interface MyWindowDelegate : NSWindowController
{
MyFieldEditor *fieldEditor;
}
#end
#implementation MyFieldEditor
-(void) setLastEditBox:(MyTextField*) box{ lastBox = box; }
-(id)init
{
if (self = [super init])
[self setFieldEditor:YES];
return self;
}
- (BOOL)resignFirstResponder
{
// Activate the last active editbox editting-end event
if(lastBox != nil)
{
[lastBox textShouldEndEditing:self];
lastBox = nil;
}
return [super resignFirstResponder];
}
#end
#implementation MyWindowDelegate
-(id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client
{
if(fieldEditor == nil) // Return our special field editor
fieldEditor = [[[MyFieldEditor alloc] autorelease] init];
return fieldEditor;
}
#end
#implementation MyTextField
- (BOOL)resignFirstResponder
{
// We're losing first responder, inform the field editor that this was the last edit box activated
MyFieldEditor* myTf = (MyFieldEditor*) [[self window] fieldEditor:YES forObject:self];
[myTf setLastEditBox:self];
return [super resignFirstResponder];
}
- (void)textDidEndEditing:(NSNotification *)notification;
{
[super textDidEndEditing:notification];
[self setStringValue:#"RECEIVED ENDEDITING"];
}
#end
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSApplication *app = [NSApplication sharedApplication];
NSRect frame = NSMakeRect(100, 100, 200, 150);
// Create the window
NSWindow* window = [[[NSWindow alloc] autorelease ] initWithContentRect:frame styleMask:NSClosableWindowMask|NSResizableWindowMask
backing:NSBackingStoreBuffered defer:NO];
[window setDelegate:[[MyWindowDelegate alloc] autorelease]];
MyTextField * tf = [ [[ MyTextField alloc ] autorelease] initWithFrame: NSMakeRect( 30.0, 100.0, 150.0, 22.0 ) ];
[ [ window contentView ] addSubview: tf ];
MyTextField * tf2 = [ [[ MyTextField alloc ] autorelease] initWithFrame: NSMakeRect( 30.0, 40.0, 150.0, 22.0 ) ];
[ [ window contentView ] addSubview: tf2 ];
[window makeKeyAndOrderFront: window];
[app run];
[pool release];
return 0;
}
You have to do only this
For key Tab
self.textfield.delegate = self;
and then implement this method
- (void)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
NSLog(#"Selector method is (%#)", NSStringFromSelector( commandSelector ) );
if (commandSelector == #selector(insertTab:)) {
//Do something against TAB key
//Or Call a Method
}
}
or see my answer at
Execute an Action when the Enter-Key is pressed in a NSTextField?
With the understanding that I mentioned in my other post, I figured out an answer. It's a little convoluted but it works. You have to subclass both the NSTextField and the NSWindow because you need information from both to set this up. Here's the subclasses:
HMTextField.h
#import <Foundation/Foundation.h>
#interface HMTextField : NSTextField {
}
#end
HMTextField.m
#import "HMTextField.h"
#import "HMWindow.h"
#implementation HMTextField
- (BOOL)becomeFirstResponder {
[(HMWindow*)[self window] setTfBecameFirstResponder:YES];
return [super becomeFirstResponder];
}
#end
HMWindow.h
#import <Foundation/Foundation.h>
#interface HMWindow : NSWindow {
BOOL tfIsFirstResponder, tfBecameFirstResponder;
}
#property (nonatomic, readwrite, assign) BOOL tfBecameFirstResponder;
#end
HMWindow.m
#import "HMWindow.h"
#implementation HMWindow
#synthesize tfBecameFirstResponder;
-(id)init {
if (self = [super init]) {
tfIsFirstResponder = NO;
}
return self;
}
- (NSResponder *)firstResponder {
id fr = [super firstResponder];
if ([fr isEqualTo:[self fieldEditor:NO forObject:nil]]) {
tfIsFirstResponder = YES;
} else {
if (tfIsFirstResponder && tfBecameFirstResponder) {
NSLog(#"the text field stopped being first responder");
tfBecameFirstResponder = NO;
}
tfIsFirstResponder = NO;
}
return fr;
}
#end
Make the classes and make your objects their class. You'll be notified of the first responder change from your text field where the NSLog message is in the HMWindow.m file. If you need help understanding how it works let me know.
Here's an example of how to indicate the appropriate time a custom NSTextFieldCell (NSCell) should draw its own bezel & focus ring (in the method [NSTextFieldCell drawWithFrame: inView]), by 'borrowing' the cell's highlight field, setting it when the text field gains focus, and clearing it when the text field loses focus (editing completes).
This technique overcomes some problems:
The cell can't easily determine if it has focus.
The cell can't easily determine which higher level component (e.g. text field or button) it belongs to to track via its parent
NSTextField can instantaneously resign first responder after gaining it, which could make it seem like it lost user focus when it didn't.
Since we're re-purposing the cell's "highlighted" state field, in order to communicate the focus state to the cell, be sure to return nil from the custom NSTextFieldCell's [highlightColorWithFrame: inView:] method.
#import "CustomTextField.h"
#implementation CustomTextField
-(BOOL)becomeFirstResponder {
((NSTextFieldCell *)self.cell).highlighted = true;
return [super becomeFirstResponder];
}
-(void)textDidEndEditing:(NSNotification *)notification {
((NSTextFieldCell *)self.cell).highlighted = false;
[super textDidEndEditing:notification];
}
#end
Complex answers. There is a simpler way to do it.
Don't forget to subclass your NSTextField to NotificableTextField and set its delegate to your view controller.
NotificableTextField.h:
#import <Cocoa/Cocoa.h>
#protocol NotificableTextFieldDelegate <NSObject>
#optional
- (void)textFieldStartedEditing:(NSTextField *)textField;
- (void)textFieldEndedEditing:(NSTextField *)textField;
#end
#interface NotificableTextField : NSTextField
#end
NotificableTextField.m:
#import "NotificableTextField.h"
#implementation NotificableTextField
- (void)awakeFromNib
{
[super awakeFromNib];
self.target = self;
self.action = #selector(inputEnd);
}
- (BOOL)becomeFirstResponder
{
BOOL status = [super becomeFirstResponder];
if (status && [self.delegate respondsToSelector:#selector(textFieldStartedEditing:)])
[(id<NotificableTextFieldDelegate>)self.delegate textFieldStartedEditing:self];
return status;
}
- (void)inputEnd
{
if ([self.delegate respondsToSelector:#selector(textFieldEndedEditing:)])
[(id<NotificableTextFieldDelegate>)self.delegate textFieldEndedEditing:self];
}
#end
NSTextField is a subclass of NSResponder. NSResponder has a method - (BOOL)resignFirstResponder. That will notify you when the NSTextField is no longer first responder... ie. loses focus. So subclass your NSTextField and do your stuff in there.

Resources