Reputation: 48649
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 initWithWindowNibName
in 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
Upvotes: 0
Views: 2666
Reputation: 1441
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.
Upvotes: 0
Reputation: 90671
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.
Upvotes: 4