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

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

现在,违规的API调用实际上是一个解决方案,我发现这里的SO:

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

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


当前回答

这不是很漂亮,但是当我不知道responder是什么时,我辞去firstResponder的方式:

创建一个UITextField,无论是在IB或编程。把它隐藏起来。如果你是用IB写的,就把它链接到你的代码上。

然后,当你想要解散键盘时,你将响应器切换到不可见的文本字段,并立即辞职:

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

其他回答

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

}

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

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

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

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

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

然而,如果第一响应器不是UIView或UIViewController的子类,这种方法将失败。

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

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

希望能有所帮助。

这不是很漂亮,但是当我不知道responder是什么时,我辞去firstResponder的方式:

创建一个UITextField,无论是在IB或编程。把它隐藏起来。如果你是用IB写的,就把它链接到你的代码上。

然后,当你想要解散键盘时,你将响应器切换到不可见的文本字段,并立即辞职:

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

而不是遍历视图集合寻找设置了isFirstResponder的视图,我也将消息发送给nil,但我存储了消息的接收器,以便我可以返回它并对它做任何我想做的事情。

此外,我将在调用本身的deferred语句中保存找到的responder的optional归零。这确保在调用结束时没有引用保留——即使是弱引用。

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实际上交还了第一个responder是什么,我可以对它做任何我想做的事情。

下面是一个示例,它检查当前的第一个响应器是否是一个带有helpMessage属性设置的UITextField,如果是,则在控件旁边的帮助气泡中显示它。我们通过屏幕上的“Quick Help”按钮来调用它。

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会为我们处理其余的事情。

下面是一个基于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 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
    }
}