我在一周前提交了我的应用程序,今天收到了可怕的拒绝电子邮件。它告诉我我的应用程序无法被接受,因为我使用的是非公共API;具体来说,它说,
您的应用程序中包含的非公共API是firstResponder。
现在,违规的API调用实际上是我在SO上找到的解决方案:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
如何在屏幕上显示当前的第一响应者?我正在寻找一种不会让我的应用被拒绝的方式。
答案 0 :(得分:529)
如果您的最终目标只是让第一响应者辞职,那么这应该有效:[self.view endEditing:YES]
答案 1 :(得分:325)
在我的一个应用程序中,我经常希望第一个响应者在用户点击背景时辞职。为此我在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`
}
答案 2 :(得分:179)
操纵第一响应者的常用方法是使用零目标操作。这是一种向响应者链发送任意消息的方式(从第一个响应者开始),然后继续向下直到有人响应消息(已经实现了与选择器匹配的方法)。
对于解雇键盘的情况,无论哪个窗口或视图是第一响应者,这都是最有效的方法:
[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
这应该比[self.view.window endEditing:YES]
更有效。
(感谢BigZaphod提醒我这个概念)
答案 3 :(得分:91)
这是一个类别,可让您通过调用[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)
答案 4 :(得分:35)
这是基于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
}
}
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
}
}
答案 5 :(得分:30)
这不是很漂亮,但当我不知道响应者是什么时,我辞职第一个响应者的方式:
在IB中或以编程方式创建UITextField。把它隐藏起来。如果您在IB中创建它,请将其链接到您的代码。
然后,当您想要关闭键盘时,将响应者切换到不可见的文本字段,并立即将其重新签名:
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
答案 6 :(得分:14)
对于Swift 3&amp; 4版nevyn's回答:
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
答案 7 :(得分:10)
这里是一个报告正确的第一响应者的解决方案(许多其他解决方案不会报告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
答案 8 :(得分:7)
第一个响应者可以是类UIResponder的任何实例,因此尽管有UIViews,还有其他类可能是第一个响应者。例如,UIViewController
也可能是第一个响应者。
在this gist中,您将找到一种递归方式,通过从应用程序窗口的rootViewController开始循环遍历控制器层次结构来获取第一个响应者。
您可以通过
检索第一个响应者- (void)foo
{
// Get the first responder
id firstResponder = [UIResponder firstResponder];
// Do whatever you want
[firstResponder resignFirstResponder];
}
但是,如果第一个响应者不是UIView或UIViewController的子类,则此方法将失败。
要解决这个问题,我们可以通过在UIResponder
上创建一个类别来执行不同的方法,并执行一些魔法调整,以便能够构建此类的所有生存实例的数组。然后,为了获得第一个响应者,我们可以简单地迭代并询问每个对象-isFirstResponder
。
可以在this other gist中找到此方法。
希望它有所帮助。
答案 9 :(得分:6)
使用 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,它将为nil。
答案 10 :(得分:5)
迭代可能是第一响应者的视图,并使用- (BOOL)isFirstResponder
来确定它们当前是否存在。
答案 11 :(得分:4)
这里的案例是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
}
}
答案 12 :(得分:4)
如果您只需要在用户点击背景区域时杀死键盘,为什么不添加手势识别器并使用它来发送[[self view] endEditing:YES]
消息?
您可以在xib或情节提要文件中添加Tap手势识别器并将其连接到某个操作
看起来像这样然后完成了
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
[[self view] endEditing:YES];
}
答案 13 :(得分:4)
Peter Steinberger关于私人通知UIWindowFirstResponderDidChangeNotification
只是tweeted,如果您想观看第一个响应者更改,您可以观察到。
答案 14 :(得分:3)
当我在用户单击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];
}
}
}
}
}
答案 15 :(得分:2)
使用UIResponder
上的类别,可以合法地要求UIApplication
对象告诉您第一响应者是谁。
见:
Is there any way of asking an iOS view which of its children has first responder status?
答案 16 :(得分:2)
这就是我在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;
}];
}
答案 17 :(得分:1)
我的方法与这里的大多数方法略有不同。我也没有遍历视图集合来寻找设置了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 for return
UIApplication.shared.sendAction(#selector(UIResponder.storeFirstResponder(_:)), to: nil, from: nil, for: nil)
// The following 'defer' statement executes after the return
// While I could use a weak ref and eliminate this, I prefer a
// hard ref which I explicitly clear as it's better for race conditions
defer {
_foundFirstResponder = nil
}
return _foundFirstResponder
}
@objc func storeFirstResponder(_ sender: AnyObject) {
_foundFirstResponder = self
}
}
然后我可以通过此操作辞去第一响应者的名字...
return UIResponder.first?.resignFirstResponder()
但是我现在也可以这样做...
if let firstResponderTextField = UIResponder?.first as? UITextField {
// bla
}
答案 18 :(得分:1)
您可以选择以下UIView
扩展名来获得(丹尼尔提供的信用证):
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
return subviews.first(where: {$0.firstResponder != nil })
}
}
答案 19 :(得分: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]);
}
}
}
}
}
}
}
答案 20 :(得分:1)
@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
}
}
答案 21 :(得分:1)
我想与您分享我在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;
}
答案 22 :(得分:1)
你可以像这样调用privite api,苹果忽略:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; SEL sel = NSSelectorFromString(@"firstResponder"); UIView *firstResponder = [keyWindow performSelector:sel];
答案 23 :(得分:1)
这是递归的好选择!无需为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;
}
答案 24 :(得分:1)
您也可以尝试这样:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event {
for (id textField in self.view.subviews) {
if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) {
[textField resignFirstResponder];
}
}
}
我没试过,但似乎是一个很好的解决方案
答案 25 :(得分: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;
}
答案 26 :(得分:0)
更新:我错了。您确实可以使用UIApplication.shared.sendAction(_:to:from:for:)
来呼叫此链接中演示的第一个响应者:http://stackoverflow.com/a/14135456/746890。
如果不在视图层次结构中,这里的大多数答案都无法真正找到当前的第一响应者。例如,AppDelegate
或UIViewController
子类。
即使第一个响应者对象不是UIView
,也可以保证找到它。
首先让我们使用next
的{{1}}属性实现它的反转版本:
UIResponder
使用这个计算属性,我们可以从下到上找到当前的第一响应者,即使它不是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
}
}
答案 27 :(得分: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
。