无需使用私有 API 即可获取当前第一响应者

发布于 2024-08-12 23:42:38 字数 459 浏览 7 评论 0原文

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

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

现在,有问题的 API 调用实际上是我在 SO:

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

How do I get the currentfirst responder on the screen? 上找到的解决方案。我正在寻找一种不会让我的应用程序被拒绝的方法。

I submitted my app a little over a week ago and got the dreaded rejection email today. It tells me that my app cannot be accepted because I'm using a non-public API; specifically, it says,

The non-public API that is included in your application is firstResponder.

Now, the offending API call is actually a solution I found here on SO:

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

How do I get the current first responder on the screen? I'm looking for a way that won't get my app rejected.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(26

波浪屿的海角声 2024-08-19 23:42:39

这就是我在用户单击 ModalViewController 中的“保存/取消”时查找 UITextField 是第一个响应者所做的事情:

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

            }
        }

    }

}

This is what I did to find what UITextField is the firstResponder when the user clicks Save/Cancel in a ModalViewController:

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

            }
        }

    }

}
汹涌人海 2024-08-19 23:42:39

这就是我的 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;
 }];
}

This is what I have in my UIViewController Category. Useful for many things, including getting first responder. Blocks are great!

- (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;
 }];
}
蓝梦月影 2024-08-19 23:42:39

您也可以这样尝试:

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

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

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

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

You can try also like this:

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

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

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

I didn't try it but it seems a good solution

梦罢 2024-08-19 23:42:39

这是递归的良好候选者!无需向 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;
}

This is good candidate for recursion! No need to add a category to UIView.

Usage (from your view controller):

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

Code:

// 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;
}
可爱咩 2024-08-19 23:42:39

@thomas-müller< 的 Swift 版本/a> 的回应

extension UIView {

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

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

        return nil
    }

}

Swift version of @thomas-müller's response

extension UIView {

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

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

        return nil
    }

}
心碎的声音 2024-08-19 23:42:39

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


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

即使第一响应者对象不是 UIView,也有一种方法可以保证您找到它。

首先让我们使用 UIRespondernext 属性来实现它的反向版本:

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

通过这个计算属性,我们可以从下到上找到当前的第一响应者,即使它不是UIView。例如,从 view 到管理它的 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

Update: I was wrong. You can indeed use UIApplication.shared.sendAction(_:to:from:for:) to call the first responder demonstrated in this link: http://stackoverflow.com/a/14135456/746890.


Most of the answers here can't really find the current first responder if it is not in the view hierarchy. For example, AppDelegate or UIViewController subclasses.

There is a way to guarantee you to find it even if the first responder object is not a UIView.

First lets implement a reversed version of it, using the next property of UIResponder:

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

With this computed property, we can find the current first responder from bottom to top even if it's not UIView. For example, from a view to the UIViewController who's managing it, if the view controller is the first responder.

However, we still need a top-down resolution, a single var to get the current first responder.

First with the view hierarchy:

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

This will search for the first responder backwards, and if it couldn't find it, it would tell its subviews to do the same thing (because its subview's next is not necessarily itself). With this we can find it from any view, including UIWindow.

And finally, we can build this:

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

So when you want to retrieve the first responder, you can call:

let firstResponder = UIResponder.first
可遇━不可求 2024-08-19 23:42:39

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

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }
        return subviews.first(where: {$0.firstResponder != nil })
    }
}

You can choose the following UIView extension to get it (credit by Daniel):

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }
        return subviews.first(where: {$0.firstResponder != nil })
    }
}
予囚 2024-08-19 23:42:39

你可以像这样调用私有API,苹果忽略:

UIWindow *keyWindow = [[UIApplication共享应用] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView *firstResponder = [keyWindow PerformSelector:sel];

you can call privite api like this ,apple ignore:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];
静水深流 2024-08-19 23:42:39

我想与您分享我在 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;
}

I would like to shared with you my implementation for find first responder in anywhere of UIView. I hope it helps and sorry for my english. Thanks

+ (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;
}
極樂鬼 2024-08-19 23:42:39

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]);
                        }
                    }
                }
            }
        }
    }
}

The solution from romeo https://stackoverflow.com/a/2799675/661022 is cool, but I noticed that the code needs one more loop. I was working with tableViewController.
I edited the script and then I checked. Everything worked perfect.

I recommed to try this:

- (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]);
                        }
                    }
                }
            }
        }
    }
}
我一向站在原地 2024-08-19 23:42:39

下面的代码有效。

- (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;
}   

Code below work.

- (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;
}   
鯉魚旗 2024-08-19 23:42:38

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

If your ultimate aim is just to resign the first responder, this should work: [self.view endEditing:YES]

青柠芒果 2024-08-19 23:42:38

在我的一个应用程序中,如果用户点击背景,我经常希望第一响应者辞职。为此,我在 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;
}

Swift:

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`
}

In one of my applications I often want the first responder to resign if the user taps on the background. For this purpose I wrote a category on UIView, which I call on the UIWindow.

The following is based on that and should return the first responder.

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

Swift:

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

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

        return nil
    }
}

Usage example in Swift:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}
不醒的梦 2024-08-19 23:42:38

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

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

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

这应该比 [self.view.window endEditing:YES]

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

A common way of manipulating the first responder is to use nil targeted actions. This is a way of sending an arbitrary message to the responder chain (starting with the first responder), and continuing down the chain until someone responds to the message (has implemented a method matching the selector).

For the case of dismissing the keyboard, this is the most effective way that will work no matter which window or view is first responder:

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

This should be more effective than even [self.view.window endEditing:YES].

(Thanks to BigZaphod for reminding me of the concept)

雄赳赳气昂昂 2024-08-19 23:42:38

这里有一个类别,允许您通过调用[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

Here's a category that allows you to quickly find the first responder by calling [UIResponder currentFirstResponder]. Just add the following two files to your project:

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

The trick here is that sending an action to nil sends it to the first responder.

(I originally published this answer here: https://stackoverflow.com/a/14135456/322427)

卖梦商人 2024-08-19 23:42:38

这是基于 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
    }
}

Swift 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
    }
}

Here is a Extension implemented in Swift based on Jakob Egger's most excellent answer:

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

Swift 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
    }
}
弄潮 2024-08-19 23:42:38

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

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

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

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];

It's not pretty, but the way I resign the firstResponder when I don't know what that the responder is:

Create an UITextField, either in IB or programmatically. Make it Hidden. Link it up to your code if you made it in IB.

Then, when you want to dismiss the keyboard, you switch the responder to the invisible text field, and immediately resign it:

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
我偏爱纯白色 2024-08-19 23:42:38

对于 Swift 3 和nevyn 答案的 4 版:

UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)

For a Swift 3 & 4 version of nevyn's answer:

UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
永言不败 2024-08-19 23:42:38

这是一个报告正确的第一响应者的解决方案(例如,许多其他解决方案不会将 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

Here's a solution which reports the correct first responder (many other solutions won't report a UIViewController as the first responder, for example), doesn't require looping over the view hierarchy, and doesn't use private APIs.

It leverages Apple's method sendAction:to:from:forEvent:, which already knows how to access the first responder.

We just need to tweak it in 2 ways:

  • Extend UIResponder so it can execute our own code on the first responder.
  • Subclass UIEvent in order to return the first responder.

Here is the code:

@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
梦途 2024-08-19 23:42:38

使用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中并像这样使用它this:

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

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

Using Swift and with a specific UIView object this might help:

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
}

Just place it in your UIViewController and use it like this:

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

Take note that the result is an Optional value so it will be nil in case no firstResponder was found in the given views subview hierarchy.

甜警司 2024-08-19 23:42:38

第一响应者可以是 UIResponder 类的任何实例,因此尽管有 UIView,但还有其他类可能是第一响应者。例如,UIViewController 也可能是第一响应者。

this gist 中,您将找到一种递归方法,通过循环遍历控制器的层次结构来获取第一个响应者从应用程序窗口的 rootViewController 开始。

您可以通过执行以下操作来检索第一响应者

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

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

但是,如果第一响应者不是 UIView 或 UIViewController 的子类,则此方法将失败。

为了解决这个问题,我们可以采用不同的方法,在 UIResponder 上创建一个类别,并执行一些神奇的调整,以便能够构建该类的所有活动实例的数组。然后,为了获得第一响应者,我们可以简单地迭代并询问每个对象是否-isFirstResponder

这种方法可以在这个其他要点中找到实现。

希望有帮助。

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.

难理解 2024-08-19 23:42:38

迭代可能是第一响应者的视图,并使用 - (BOOL)isFirstResponder 来确定它们当前是否是。

Iterate over the views that could be the first responder and use - (BOOL)isFirstResponder to determine if they currently are.

过潦 2024-08-19 23:42:38

我也向 nil 发送一条消息,而不是遍历视图集合来查找设置了 isFirstResponder 的视图,但我存储了消息的接收者,以便我可以返回它并用它做我想做的事。

此外,我将调用本身中的 defer 语句中保存找到的响应者的可选值清零。这可以确保在调用结束时不会保留任何引用(即使是较弱的引用)。

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
    }

    // Make sure to mark this with '@objc' since it has to be reachable as a selector for `sendAction`
    @objc func storeFirstResponder(_ sender: AnyObject) {

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

通过上面的内容,我可以通过简单地执行此操作来辞职第一响应者...

UIResponder.first?.resignFirstResponder()

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

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

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 负责处理为我们休息。

Rather than iterate through the collection of views looking for the one that has isFirstResponder set, I too send a message to nil, but I store the receiver of the message so I can return it and do whatever I wish with it.

Additionally, I zero out the optional that holds the found responder in a defer statement from within the call itself. This ensures no references remain--even weak ones--at the end of the call.

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
    }

    // Make sure to mark this with '@objc' since it has to be reachable as a selector for `sendAction`
    @objc func storeFirstResponder(_ sender: AnyObject) {

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

With the above, I can resign the first responder by simply doing this...

UIResponder.first?.resignFirstResponder()

But since my API actually hands back whatever the first responder is, I can do whatever I want with it.

Here's an example that checks if the current first responder is a UITextField with a helpMessage property set, and if so, shows it in a help bubble right next to the control. We call this from a 'Quick Help' button on our screen.

func showQuickHelp(){

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

The support for the above is defined in an extension on UITextField like so...

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

Now to support this feature, all we have to do is decide which text fields have help messages and the UI takes care of the rest for us.

奶气 2024-08-19 23:42:38

Peter Steinberger 刚刚推文关于私人通知 UIWindowFirstResponderDidChangeNotification,您可以观察到如果你想观看firstResponder的变化。

Peter Steinberger just tweeted about the private notification UIWindowFirstResponderDidChangeNotification, which you can observe if you want to watch the firstResponder change.

睡美人的小仙女 2024-08-19 23:42:38

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

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

看起来像这样然后完成

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}

If you just need to kill the keyboard when the user taps on a background area why not add a gesture recognizer and use it to send the [[self view] endEditing:YES] message?

you can add the Tap gesture recogniser in the xib or storyboard file and connect it to an action,

looks something like this then finished

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}
凉世弥音 2024-08-19 23:42:38

这就是 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
    }

}

Just it case here is Swift version of awesome Jakob Egger's approach:

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
    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文