获取当前的第一响应者,而不使用私人API

我在一个多星期前提交了我的应用程序,今天收到了令人恐惧的拒绝电子邮件。 它告诉我,我的应用程序不能被接受,因为我使用非公开的API; 具体来说,

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

现在,有问题的API调用实际上是我在这里find的解决scheme:

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

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

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

如果你的最终目的只是[self.view endEditing:YES]第一响应者,这应该工作: [self.view endEditing:YES]

操纵第一响应者的常用方式是使用无针对性的动作。 这是一种向响应者链发送任意消息(从第一响应者开始)的方式,并继续下去,直到有人响应消息(已经实现了与select器匹配的方法)。

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

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

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

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

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

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 

这里的诀窍是发送一个动作到零发送给第一个响应者。

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

这不是很好,但是当我不知道响应者是什么的时候,我辞去了第一个响应者的方式:

创build一个UITextField,在IB或编程。 让它隐藏。 把它连接到你的代码,如果你在IB。

然后,当您要closures键盘时,您将响应者切换到不可见的文本字段,并立即辞职:

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

这是一个基于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 first: 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 } } 

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

它利用苹果的方法sendAction::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 

第一个响应者可以是UIResponder类的任何实例,所以还有其他类可能是第一个响应者,尽pipeUIViews。 例如UIViewController也可能是第一个响应者。

在这个要点中,你将会find一个recursion的方式来获取第一个响应者,从应用程序的窗口的rootViewController开始循环控制器的层次结构。

你可以通过这样做来检索第一个响应者

 - (void)foo { // Get the first responder id firstResponder = [UIResponder firstResponder]; // Do whatever you want [firstResponder resignFirstResponder]; } 

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

为了解决这个问题,我们可以通过在UIResponder上创build一个类别来执行不同的方法,并执行一些魔法转换,以便能够构build这个类的所有实例的数组。 然后,为了得到第一个响应者,我们可以简单地迭代并询问每个对象是否是-isFirstResponder

这种方法可以在这个其他的要点中find 。

希望它有帮助。

对于一个Swift 3版本的Nevyn的回答:

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

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

使用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) 

请注意,结果是一个可选值,所以如果在给定的视图子视图中找不到firstResponser,它将为零。

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

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

看起来像这样然后完成

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

这是我做了什么,当用户在ModalViewController中单击保存/取消时,find什么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]; } } } } } 

彼得·斯坦伯格只是啾啾私人通知UIWindowFirstResponderDidChangeNotification ,你可以观察,如果你想观看第一个应答的变化。

就这样,这里是Swift版本的真棒Jakob Egger的方法:

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

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

对于UIResponder类别,可以合法地请求UIApplication对象告诉你第一个响应者是谁。

看到这个:

有什么办法可以让iOS查看哪些孩子有第一响应者状态?

你可以这样称呼privite api,苹果忽略:

 UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; SEL sel = NSSelectorFromString(@"firstResponder"); UIView *firstResponder = [keyWindow performSelector:sel]; 

你也可以尝试这样的:

 - (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event { for (id textField in self.view.subviews) { if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) { [textField resignFirstResponder]; } } } 

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

从romeo的解决scheme是很酷的,但我注意到,代码需要一个循环。 我正在使用tableViewController。 我编辑了脚本,然后检查了一下。 一切都很完美。

我build议试试这个:

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

这是recursion的好select! 不需要添加一个类别到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; } 

我想与你分享我的实现在UIView的任何地方find第一响应者。 我希望这对我的英语有帮助和抱歉。 谢谢

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

}

迅速的@ thomas-müller版本的回应

 extension UIView { func firstResponder() -> UIView? { if self.isFirstResponder() { return self } for subview in self.subviews { if let firstResponder = subview.firstResponder() { return firstResponder } } return nil } } 

下面的代码工作。

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