有没有方法询问一个iOS视图中哪个子视图具有第一响应者状态?

47

在 Mac OS X 中,您可以通过以下方式查找第一响应者:

[[self window] firstResponder]

在iOS中有没有方法可以做到这一点?或者你需要枚举子控件并向每个控件发送一个isFirstResponder消息吗?

4个回答

253

我真的很喜欢VJK的解决方案,但像MattDiPasquale建议的那样,它似乎比必要的要复杂。所以我写了这个更简单的版本:

Objective-C

UIResponder+FirstResponder.h:

#import <UIKit/UIKit.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

Swift 4

import UIKit

extension UIResponder {

    private static weak var _currentFirstResponder: UIResponder?

    static var currentFirstResponder: UIResponder? {
        _currentFirstResponder = nil
        UIApplication.shared.sendAction(#selector(UIResponder.findFirstResponder(_:)), to: nil, from: nil, for: nil)

        return _currentFirstResponder
    }

    @objc func findFirstResponder(_ sender: Any) {
        UIResponder._currentFirstResponder = self
    }
}

我觉得把它变成一个类方法更有意义,你现在可以这样找到第一响应者:[UIResponder currentFirstResponder]



18
这非常简单,做得很好。 - Steven Fisher
2
instancetype会很合适 :) - Arcank
4
对于信息,视图控制器可以成为第一响应者。您需要做的就是从-canBecomeFirstResponder方法中返回YES。这对于始终在屏幕上或处理快捷方式而没有视图聚焦的输入附件视图非常有用。因此,在此处使用UIView *是错误的。 - Max Seelemann
3
这里有一个Swift的实现:https://dev59.com/23I-5IYBdhLWcg3wf4dD#27140764 - Code Commander
3
在iOS9上至少无法使用。我将焦点放在位于滚动视图中的“TextField”上。按照所描述的方式检查“firstResponder”,并将ScrollView作为第一个响应者。 同时,“[[[[UIApplication sharedApplication] windows] firstObject] valueForKey:@"firstResponder"]”将返回TextField。 - malex
显示剩余13条评论

26

我编写了一个关于 UIResponder 的分类来查找第一响应者。

@interface UIResponder (firstResponder)
- (id) currentFirstResponder;
@end

并且

#import <objc/runtime.h>
#import "UIResponder+firstResponder.h"

static char const * const aKey = "first";

@implementation UIResponder (firstResponder)

- (id) currentFirstResponder {
    [[UIApplication sharedApplication] sendAction:@selector(findFirstResponder:) to:nil from:self forEvent:nil];
    id obj = objc_getAssociatedObject (self, aKey);
    objc_setAssociatedObject (self, aKey, nil, OBJC_ASSOCIATION_ASSIGN);
    return obj;
}

- (void) setCurrentFirstResponder:(id) aResponder {
    objc_setAssociatedObject (self, aKey, aResponder, OBJC_ASSOCIATION_ASSIGN);
}

- (void) findFirstResponder:(id) sender {
    [sender setCurrentFirstResponder:self];
}

@end

然后在任何继承自 UIResponder 的类中,您可以通过调用以下方法来获取第一个响应者:

UIResponder* aFirstResponder = [self currentFirstResponder];

但要记得先导入UIResponder类别接口文件!

这使用了文档化的API,所以不应该有应用商店拒绝问题。


4
使用 -[UIApplication sendAction:to:from:forEvent:] 很方便!:) 但是,为什么不使用静态变量来引用 firstResponder 呢?因为一次只会有一个 firstResponder,所以不需要使用关联对象。 - ma11hew28
哦,这真聪明!在我阅读文档之前,我无法理解你的 sendAction:。那一定是 iOS API 中唯一直接访问第一响应者的部分。 - sobri
仅返回请求当前FirstResponder的视图。当我检查响应者是否符合预期的第一响应者时,它会显示不是。 - AppHandwerker
我知道这已经有些过时了,而且还有一个被接受的(并且得到了更多赞同)基于这个答案的解决方案,但这是一个非常好的解决方案。谢谢。 - ChrisH

14

如果你需要第一个响应者对象仅仅是为了让它放弃响应者的状态,那么这里有一种方法可以使得任何一个响应者放弃其状态。UIView有一个方法可以迭代所有子视图,并要求任何第一个响应者放弃其状态。

[[self view] endEditing:YES];

这里有一个指向苹果UIView文档的链接 "该方法查找当前视图及其子视图层次结构中当前是第一响应者的文本字段。如果找到,则请求该文本字段放弃作为第一响应者的身份。如果force参数设置为YES,则不会询问文本字段,而是强制其放弃第一响应者身份。"


2
我不认为 OP 想要强制使其放弃第一个响应者,只是想找到第一个响应者。 - Tom H
2
然而,对于人们发现该片段来说,这是一个不错的地方。 - Tommy Herbert

12
你需要迭代所有子控件并测试 isFirstResponder 属性。当你遇到 TRUE 时,退出循环。
UIView *firstResponder;
for (UIView *view in self.view.subviews) //: caused error
{
    if (view.isFirstResponder)
    {
        firstResponder = view;
        break;
    }
}

更好的解决方案

请参考Jakob的答案


7
这段代码只搜索视图的直接子级,但是当第一响应者嵌套更深时,它无法找到它。 - Jakob Egger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接