355

一个多星期前我提交了我的应用程序,今天收到了可怕的拒绝电子邮件。它告诉我无法接受我的应用程序,因为我使用的是非公共 API;具体来说,它说,

应用程序中包含的非公共 API 是 firstResponder。

现在,有问题的 API 调用实际上是我在 SO 上找到的解决方案:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView   *firstResponder = [keyWindow performSelector:@selector(firstResponder)];

如何在屏幕上显示当前的第一响应者?我正在寻找一种不会让我的应用程序被拒绝的方法。

4

28 回答 28

534

如果您的最终目标只是让第一响应者辞职,那么这应该可行:[self.view endEditing:YES]

于 2010-05-22T10:13:16.140 回答
354

在我的一个应用程序中,如果用户点击背景,我经常希望第一响应者辞职。为此,我在 UIView 上编写了一个类别,我在 UIWindow 上调用它。

以下内容基于此,应返回第一响应者。

@implementation UIView (FindFirstResponder)
- (id)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;        
    }
    for (UIView *subView in self.subviews) {
        id responder = [subView findFirstResponder];
        if (responder) return responder;
    }
    return nil;
}
@end

iOS 7+

- (id)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;
    }
    for (UIView *subView in self.view.subviews) {
        if ([subView isFirstResponder]) {
            return subView;
        }
    }
    return nil;
}

迅速:

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }

        for subview in subviews {
            if let firstResponder = subview.firstResponder {
                return firstResponder
            }
        }

        return nil
    }
}

Swift 中的用法示例:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}
于 2009-12-01T00:43:38.890 回答
193

操纵第一响应者的一种常见方法是使用 nil 目标操作。这是一种将任意消息发送到响应者链(从第一个响应者开始)并继续沿链向下直到有人响应消息(已实现与选择器匹配的方法)的方式。

对于关闭键盘的情况,无论哪个窗口或视图是第一响应者,这都是最有效的方法:

[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];

这应该比甚至更有效[self.view.window endEditing:YES]

(感谢BigZaphod提醒我这个概念)

于 2012-08-01T22:05:57.080 回答
106

这是一个类别,可让您通过调用快速找到第一响应者[UIResponder currentFirstResponder]。只需将以下两个文件添加到您的项目中:

UIResponder+FirstResponder.h:

#import <Cocoa/Cocoa.h>
@interface UIResponder (FirstResponder)
    +(id)currentFirstResponder;
@end

UIResponder+FirstResponder.m:

#import "UIResponder+FirstResponder.h"
static __weak id currentFirstResponder;
@implementation UIResponder (FirstResponder)
    +(id)currentFirstResponder {
         currentFirstResponder = nil;
         [[UIApplication sharedApplication] sendAction:@selector(findFirstResponder:) to:nil from:nil forEvent:nil];
         return currentFirstResponder;
    }
    -(void)findFirstResponder:(id)sender {
        currentFirstResponder = self;
    }
@end

这里的诀窍是向 nil 发送一个动作会将其发送给第一响应者。

(我最初在这里发布了这个答案:https ://stackoverflow.com/a/14135456/322427 )

于 2014-01-24T10:54:48.570 回答
48

这是基于 Jakob Egger 最优秀的答案在 Swift 中实现的扩展:

import UIKit

extension UIResponder {
    // Swift 1.2 finally supports static vars!. If you use 1.1 see: 
    // http://stackoverflow.com/a/24924535/385979
    private weak static var _currentFirstResponder: UIResponder? = nil
    
    public class func currentFirstResponder() -> UIResponder? {
        UIResponder._currentFirstResponder = nil
        UIApplication.sharedApplication().sendAction("findFirstResponder:", to: nil, from: nil, forEvent: nil)
        return UIResponder._currentFirstResponder
    }
    
    internal func findFirstResponder(sender: AnyObject) {
        UIResponder._currentFirstResponder = self
    }
}

斯威夫特 4

import UIKit    

extension UIResponder {
    private weak static var _currentFirstResponder: UIResponder? = nil
    
    public static var current: UIResponder? {
        UIResponder._currentFirstResponder = nil
        UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
        return UIResponder._currentFirstResponder
    }
    
    @objc internal func findFirstResponder(sender: AnyObject) {
        UIResponder._currentFirstResponder = self
    }
}
于 2014-11-26T03:20:37.593 回答
29

这并不漂亮,但是当我不知道响应者是什么时,我辞去 firstResponder 的方式:

在 IB 中或以编程方式创建 UITextField。使其隐藏。如果您在 IB 中制作,请将其链接到您的代码。

然后,当您想关闭键盘时,将响应者切换到不可见的文本字段,并立即将其退出:

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
于 2010-05-24T18:33:29.113 回答
21

对于nevyn 的 Swift 3 和 4 版本的回答:

UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
于 2017-01-18T08:35:36.853 回答
11

这是一个报告正确的第一响应者的解决方案(例如,许多其他解决方案不会将 a 报告UIViewController为第一响应者),不需要遍历视图层次结构,并且不使用私有 API。

它利用 Apple 的方法sendAction:to:from:forEvent:,该方法已经知道如何访问第一响应者。

我们只需要通过两种方式对其进行调整:

  • 扩展UIResponder以便它可以在第一响应者上执行我们自己的代码。
  • 子类UIEvent以返回第一响应者。

这是代码:

@interface ABCFirstResponderEvent : UIEvent
@property (nonatomic, strong) UIResponder *firstResponder;
@end

@implementation ABCFirstResponderEvent
@end

@implementation UIResponder (ABCFirstResponder)
- (void)abc_findFirstResponder:(id)sender event:(ABCFirstResponderEvent *)event {
    event.firstResponder = self;
}
@end

@implementation ViewController

+ (UIResponder *)firstResponder {
    ABCFirstResponderEvent *event = [ABCFirstResponderEvent new];
    [[UIApplication sharedApplication] sendAction:@selector(abc_findFirstResponder:event:) to:nil from:nil forEvent:event];
    return event.firstResponder;
}

@end
于 2014-07-08T18:46:25.553 回答
8

The first responder can be any instance of the class UIResponder, so there are other classes that might be the first responder despite the UIViews. For example UIViewController might also be the first responder.

In this gist you will find a recursive way to get the first responder by looping through the hierarchy of controllers starting from the rootViewController of the application's windows.

You can retrieve then the first responder by doing

- (void)foo
{
    // Get the first responder
    id firstResponder = [UIResponder firstResponder];

    // Do whatever you want
    [firstResponder resignFirstResponder];      
}

However, if the first responder is not a subclass of UIView or UIViewController, this approach will fail.

To fix this problem we can do a different approach by creating a category on UIResponder and perform some magic swizzeling to be able to build an array of all living instances of this class. Then, to get the first responder we can simple iterate and ask each object if -isFirstResponder.

This approach can be found implemented in this other gist.

Hope it helps.

于 2012-05-11T16:06:42.303 回答
8

使用Swift特定UIView对象这可能会有所帮助:

func findFirstResponder(inView view: UIView) -> UIView? {
    for subView in view.subviews as! [UIView] {
        if subView.isFirstResponder() {
            return subView
        }
        
        if let recursiveSubView = self.findFirstResponder(inView: subView) {
            return recursiveSubView
        }
    }
    
    return nil
}

只需将它放在你的UIViewController并像这样使用它:

let firstResponder = self.findFirstResponder(inView: self.view)

请注意,结果是一个可选值,因此如果在给定的视图子视图层次结构中找不到 firstResponder,它将为零。

于 2015-05-05T16:29:33.080 回答
6

遍历可能是第一响应者的视图,并用于- (BOOL)isFirstResponder确定它们当前是否是。

于 2009-12-01T00:37:04.627 回答
5

Peter Steinberger 刚刚发布了关于私人通知的UIWindowFirstResponderDidChangeNotification文,如果您想观察 firstResponder 的变化,您可以观察它。

于 2013-05-07T14:55:33.227 回答
5

如果您只需要在用户点击背景区域时关闭键盘,为什么不添加手势识别器并使用它来发送[[self view] endEditing:YES]消息?

您可以在 xib 或 storyboard 文件中添加 Tap 手势识别器并将其连接到一个动作,

看起来像这样然后完成

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}
于 2013-06-27T11:28:30.250 回答
5

我的方法与大多数人略有不同。我没有遍历视图集合以查找已isFirstResponder设置的视图,而是向 发送消息nil,但我存储了消息的接收者,以便我可以返回它并用它做任何我想做的事情。

import UIKit

private var _foundFirstResponder: UIResponder? = nil

extension UIResponder {

    static var first:UIResponder? {

        // Sending an action to 'nil' implicitly sends it to the first responder
        // where we simply capture it and place it in the _foundFirstResponder variable.
        // As such, the variable will contain the current first responder (if any) immediately after this line executes
        UIApplication.shared.sendAction(#selector(UIResponder.storeFirstResponder(_:)), to: nil, from: nil, for: nil)

        // The following 'defer' statement runs *after* this getter returns,
        // thus releasing any strong reference held by the variable immediately thereafter
        defer {
            _foundFirstResponder = nil
        }

        // Return the found first-responder (if any) back to the caller
        return _foundFirstResponder
    }

    @objc func storeFirstResponder(_ sender: AnyObject) {

        // Capture the recipient of this message (self), which is the first responder
        _foundFirstResponder = self
    }
}

有了以上内容,我可以通过简单地这样做来辞去第一响应者的职务......

UIResponder.first?.resignFirstResponder()

但由于我的 API 实际上交还了第一响应者是什么,所以我可以用它做任何我想做的事情。

这是一个检查当前第一响应者是否UITextField具有helpMessage属性集的示例,如果是,则将其显示在控件旁边的帮助气泡中。我们通过屏幕上的“快速帮助”按钮调用它。

func showQuickHelp(){

    if let textField   = UIResponder?.first as? UITextField,
       let helpMessage = textField.helpMessage {
    
        textField.showHelpBubble(with:helpMessage)
    }
}

对上述内容的支持是在UITextField类似这样的扩展中定义的......

extension UITextField {
    var helpMessage:String? { ... }
    func showHelpBubble(with message:String) { ... }
}

现在要支持此功能,我们所要做的就是确定哪些文本字段有帮助消息,而 UI 会为我们处理剩下的事情。

于 2018-10-15T19:45:59.513 回答
5

只是这里是真棒 Jakob Egger 方法的 Swift 版本:

import UIKit

private weak var currentFirstResponder: UIResponder?

extension UIResponder {

    static func firstResponder() -> UIResponder? {
        currentFirstResponder = nil
        UIApplication.sharedApplication().sendAction(#selector(self.findFirstResponder(_:)), to: nil, from: nil, forEvent: nil)
        return currentFirstResponder
    }

    func findFirstResponder(sender: AnyObject) {
        currentFirstResponder = self
    }

}
于 2016-05-27T21:13:03.630 回答
4

当用户在 ModalViewController 中单击 Save/Cancel 时,这就是我为找到 UITextField 是 firstResponder 所做的:

    NSArray *subviews = [self.tableView subviews];

for (id cell in subviews ) 
{
    if ([cell isKindOfClass:[UITableViewCell class]]) 
    {
        UITableViewCell *aCell = cell;
        NSArray *cellContentViews = [[aCell contentView] subviews];
        for (id textField in cellContentViews) 
        {
            if ([textField isKindOfClass:[UITextField class]]) 
            {
                UITextField *theTextField = textField;
                if ([theTextField isFirstResponder]) {
                    [theTextField resignFirstResponder];
                }

            }
        }

    }

}
于 2010-05-10T00:08:55.700 回答
3

使用 类别UIResponder,可以合法地要求UIApplication对象告诉您谁是第一响应者。

看到这个:

有什么方法可以询问 iOS 视图的哪个子视图具有第一响应者状态?

于 2012-04-29T14:43:21.457 回答
3

这就是我的 UIViewController 类别中的内容。对许多事情有用,包括获得第一响应者。积木很棒!

- (UIView*) enumerateAllSubviewsOf: (UIView*) aView UsingBlock: (BOOL (^)( UIView* aView )) aBlock {

 for ( UIView* aSubView in aView.subviews ) {
  if( aBlock( aSubView )) {
   return aSubView;
  } else if( ! [ aSubView isKindOfClass: [ UIControl class ]] ){
   UIView* result = [ self enumerateAllSubviewsOf: aSubView UsingBlock: aBlock ];

   if( result != nil ) {
    return result;
   }
  }
 }    

 return nil;
}

- (UIView*) enumerateAllSubviewsUsingBlock: (BOOL (^)( UIView* aView )) aBlock {
 return [ self enumerateAllSubviewsOf: self.view UsingBlock: aBlock ];
}

- (UIView*) findFirstResponder {
 return [ self enumerateAllSubviewsUsingBlock:^BOOL(UIView *aView) {
  if( [ aView isFirstResponder ] ) {
   return YES;
  }

  return NO;
 }];
}
于 2012-01-24T05:32:09.643 回答
3

您可以选择以下UIView扩展来获取它(Daniel 提供)

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }
        return subviews.first(where: {$0.firstResponder != nil })
    }
}
于 2019-05-17T08:27:24.653 回答
2

这是递归的好候选!无需向 UIView 添加类别。

用法(来自您的视图控制器):

UIView *firstResponder = [self findFirstResponder:[self view]];

代码:

// This is a recursive function
- (UIView *)findFirstResponder:(UIView *)view {

    if ([view isFirstResponder]) return view; // Base case

    for (UIView *subView in [view subviews]) {
        if ([self findFirstResponder:subView]) return subView; // Recursion
    }
    return nil;
}
于 2013-10-10T23:54:46.480 回答
2

你也可以这样尝试:

- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event { 

    for (id textField in self.view.subviews) {

        if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) {
            [textField resignFirstResponder];
        }
    }
} 

我没有尝试,但它似乎是一个很好的解决方案

于 2011-09-01T09:44:53.740 回答
2

你可以像这样调用privite api,苹果忽略:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];
于 2014-11-11T10:11:27.720 回答
2

我想与您分享我在 UIView 的任何位置查找第一响应者的实现。我希望它对我的英语有所帮助和抱歉。谢谢

+ (UIView *) findFirstResponder:(UIView *) _view {

    UIView *retorno;

    for (id subView in _view.subviews) {

        if ([subView isFirstResponder])
        return subView;

        if ([subView isKindOfClass:[UIView class]]) {
            UIView *v = subView;

            if ([v.subviews count] > 0) {
                retorno = [self findFirstResponder:v];
                if ([retorno isFirstResponder]) {
                    return retorno;
                }
            }
        }
    }

    return retorno;
}
于 2015-09-22T12:21:56.963 回答
2

@thomas-müller回复的Swift 版本

extension UIView {

    func firstResponder() -> UIView? {
        if self.isFirstResponder() {
            return self
        }

        for subview in self.subviews {
            if let firstResponder = subview.firstResponder() {
                return firstResponder
            }
        }

        return nil
    }

}
于 2016-04-04T15:15:39.580 回答
1

来自 romeo https://stackoverflow.com/a/2799675/661022的解决方案很酷,但我注意到代码还需要一个循环。我正在使用 tableViewController。我编辑了脚本,然后我检查了。一切都很完美。

我建议试试这个:

- (void)findFirstResponder
{
    NSArray *subviews = [self.tableView subviews];
    for (id subv in subviews )
    {
        for (id cell in [subv subviews] ) {
            if ([cell isKindOfClass:[UITableViewCell class]])
            {
                UITableViewCell *aCell = cell;
                NSArray *cellContentViews = [[aCell contentView] subviews];
                for (id textField in cellContentViews)
                {
                    if ([textField isKindOfClass:[UITextField class]])
                    {
                        UITextField *theTextField = textField;
                        if ([theTextField isFirstResponder]) {
                            NSLog(@"current textField: %@", theTextField);
                            NSLog(@"current textFields's superview: %@", [theTextField superview]);
                        }
                    }
                }
            }
        }
    }
}
于 2014-05-29T11:43:48.247 回答
1

更新:我错了。您确实可以UIApplication.shared.sendAction(_:to:from:for:)用来调用此链接中演示的第一响应者:http ://stackoverflow.com/a/14135456/746890 。


如果当前的第一响应者不在视图层次结构中,这里的大多数答案都无法真正找到当前的第一响应者。例如,AppDelegateUIViewController子类。

即使第一响应者对象不是UIView.

首先让我们使用 的next属性实现它的反转版本UIResponder

extension UIResponder {
    var nextFirstResponder: UIResponder? {
        return isFirstResponder ? self : next?.nextFirstResponder
    }
}

有了这个计算属性,我们可以从下到上找到当前的第一响应者,即使它不是UIView。例如,如果视图控制器是第一响应者,则从 aview到管理它的人。UIViewController

但是,我们仍然需要一个自上而下的分辨率,一个单一var的来获取当前的第一响应者。

首先是视图层次结构:

extension UIView {
    var previousFirstResponder: UIResponder? {
        return nextFirstResponder ?? subviews.compactMap { $0.previousFirstResponder }.first
    }
}

这将向后搜索第一响应者,如果找不到,它会告诉它的子视图做同样的事情(因为它的子视图next不一定是它自己)。有了这个,我们可以从任何视图中找到它,包括UIWindow.

最后,我们可以构建这个:

extension UIResponder {
    static var first: UIResponder? {
        return UIApplication.shared.windows.compactMap({ $0.previousFirstResponder }).first
    }
}

因此,当您要检索第一响应者时,可以调用:

let firstResponder = UIResponder.first
于 2018-05-22T16:17:06.690 回答
0

找到第一响应者的最简单方法:

func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool

默认实现将 action 方法分派给给定的目标对象,或者,如果没有指定目标,则分派给第一响应者。

下一步:

extension UIResponder
{
    private weak static var first: UIResponder? = nil

    @objc
    private func firstResponderWhereYouAre(sender: AnyObject)
    {
        UIResponder.first = self
    }

    static var actualFirst: UIResponder?
    {
        UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
        return UIResponder.first
    }
}

用法:只是UIResponder.actualFirst为了你自己的目的。

于 2019-10-14T16:49:02.063 回答
0

下面的代码工作。

- (id)ht_findFirstResponder
{
    //ignore hit test fail view
    if (self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES) {
        return nil;
    }
    if ([self isKindOfClass:[UIControl class]] && [(UIControl *)self isEnabled] == NO) {
        return nil;
    }

    //ignore bound out screen
    if (CGRectIntersectsRect(self.frame, [UIApplication sharedApplication].keyWindow.bounds) == NO) {
        return nil;
    }

    if ([self isFirstResponder]) {
        return self;
    }

    for (UIView *subView in self.subviews) {
        id result = [subView ht_findFirstResponder];
        if (result) {
            return result;
        }
    }
    return nil;
}   
于 2016-11-15T13:27:41.057 回答