获取当前的第一响应者,而不使用私人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; }