如何让自定义视图中的NSTextField成为第一个响应者?

时间:2015-05-19 02:39:15

标签: cocoa xcode6

我的理解是,将文本字段设为 第一响应者 意味着您可以在文本字段中输入文本,而无需先单击文本字段。

我制作了一个简单的应用来证明这个问题。以下是文件:

//
//  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

MyView.xib的文件所有者是MyViewController,这里是文件所有者的出口:

enter image description here

回答回复:

1)这对我有用:

- (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)是的,我正在尝试为窗口的初始显示做第一响应者舞蹈,但这对我不起作用:

@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]];



}

窗口显示正常,但我必须单击文本字段才能输入文本。如果我改为使用makeFirstResponder:,那么文本字段就像我想要的那样运行:

@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]];



}

我在Apple Developer's Event Handling Basics,设置第一响应者部分找到了该建议。

对回答评论的回复:

在我的应用中,如果我选择MainMenu.xib然后取消选中Visible at Launch,则下面的代码会成功查看“查看第一响应者”中的文本字段:

//
//  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

如果我创建一个WindowController(以及.xib,并删除MainMenu.xib中的窗口),那么我最有意义的是组织配置/初始化,如下所示:

//
//  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

然后通过覆盖WindowController中的initWithWindowNibName来设置View:

//
//  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

2 个答案:

答案 0 :(得分:4)

有几件事:

  1. 你不应该自己打电话给-becomeFirstResponder(除非在覆盖中打电话给超级)。它在docs

    中说明了
      

    永远不要直接调用此方法。

    该方法是系统通知一个对象,它即将成为第一个响应者并给它一个拒绝的机会。它实际上并没有使对象成为第一响应者。

    查看第一个响应者的方法是首先检查它是否接受该状态,然后告诉窗口使其成为第一个响应者:

    if ([self.textField acceptsFirstResponder])
        [self.textField.window makeFirstResponder:self.textField];
    
  2. 您正在尝试在包含视图的视图控制器初始化期间使文本字段成为第一个响应者。这太早了。此时视图不能位于窗口中。因此,它不能成为窗口的第一响应者。您应该在将视图添加到窗口后执行此操作。

    该作业属于窗口控制器,而不属于视图控制器。毕竟,它只是具有必要概览的窗口控制器,以决定哪个(可能很多)视图应该是第一响应者。您在这个简单的应用程序中没有使用单独的窗口控制器,因此责任将落在AppDelegate类中。 (根据您发布的代码,它有效地充当了窗口的控制器。)

  3. 如果在显示窗口之前发生这种情况,您应该考虑设置窗口initialFirstResponder而不是强制视图立即成为第一个响应者。首次显示时,窗口将使initialFirstResponder成为第一个响应者。

    最好保留-makeFirstResponder:,以便在显示窗口后以编程方式更改第一响应者。

答案 1 :(得分:0)

在初始化期间立即设置第一响应者可以忽略。特别是对于附加到除Window Controller之外的View Controller的UI组件。 viewWillAppear()和viewDidAppear()之间可能存在延迟。

可以使用window的initialFirstResponder属性,或makeFirstResponder()方法来完成...

window?.initialFirstResponder = yourVC.yourTextField
//OR
window?.makeFirstResponder(yourVC.yourTextField)

(对不起,我的大脑和手已弃用Objective-C)

仅供参考,窗口属性可以在某些地方以各种方式访问​​。 在您的自定义VC中,它将是:

self.yourTextField.window?
//OR
NSApplication.sharedApplication().mainWindow?

在自定义WindowController中:

self.window?
//or
NSApplication.sharedApplication().mainWindow?

在你的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?

但就像我说的那样,如果你立刻设置它,可以在luanching期间忽略它。所以你在延迟后设置它:

window?.performSelector(#selector(window?.makeFirstResponder(_:)),
                                  withObject: .yourTextField,
                                  afterDelay:0.5)

根据您放置它的位置,&#39;窗口的点符号?&#39;和&#39; .yourTextField&#39;零件应该是不同的。