在不使用私有API的情况下获取当前的第一响应者

Jus*_*ble 334 cocoa-touch objective-c first-responder

我在一周前提交了我的应用程序,今天收到了可怕的拒绝电子邮件.它告诉我我的应用程序无法被接受,因为我使用的是非公共API; 具体来说,它说,

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

现在,违规的API调用实际上是我在SO上找到的解决方案:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView   *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
Run Code Online (Sandbox Code Playgroud)

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

cdy*_*n37 529

如果你的最终目的只是让第一响应者辞职,这应该有效: [self.view endEditing:YES]

  • 对于所有人说这是"问题"的答案,你能看看实际的问题吗?问题是如何获得当前的第一响应者.不是如何辞职第一响应者. (119认同)
  • 确实,这并不能完全回答这个问题,但显然这是一个非常有用的答案.谢谢! (7认同)
  • +1,即使它不是问题的答案.为什么?因为很多人来到这里有一个问题,这个答案答案:)至少267(此刻)他们... (5认同)
  • 这不是回答问题。 (3认同)
  • 我们应该在哪里称之为self.view endEditing? (2认同)

Tho*_*ler 330

在我的一个应用程序中,我经常希望第一个响应者在用户点击背景时辞职.为此,我在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
Run Code Online (Sandbox Code Playgroud)

iOS 7+

- (id)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;
    }
    for (UIView *subView in self.view.subviews) {
        if ([subView isFirstResponder]) {
            return subView;
        }
    }
    return nil;
}
Run Code Online (Sandbox Code Playgroud)

迅速:

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }

        for subview in subviews {
            if let firstResponder = subview.firstResponder {
                return firstResponder
            }
        }

        return nil
    }
}
Run Code Online (Sandbox Code Playgroud)

Swift中的用法示例:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么这比简单地调用`[self.view endEditing:YES]`更好? (276认同)
  • @Tim我不是你能做到的.这显然是一种更简单的方式来辞职第一响应者.然而,它对原始问题没有帮助,这是为了识别第一响应者. (66认同)
  • 此外,这是一个更好的答案,因为你可能想要与第一个响应者做一些事情而不是辞职... (16认同)
  • 为什么iOS 7的区别?难道你不想在这种情况下递归吗? (10认同)
  • 应该注意的是,这只能找到UIViews,而不是其他也可能是第一个响应者的UIR应答器(例如UIViewController或UIApplication). (3认同)
  • 此代码在技术上是正确的,但不应再次使用和agan.正确的方式来辞职第一响应者是http://stackoverflow.com/a/11768282/2648673 (3认同)

nev*_*vyn 183

操纵第一响应者的常用方法是使用零目标操作.这是一种向响应者链发送任意消息(从第一个响应者开始),然后继续向下直到某人响应消息(已实现与选择器匹配的方法)的方式.

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

[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这太酷了.可能是我在SO上看过的最新的Cocoa Touch技巧.帮助我永无止境! (4认同)
  • 该解决方案是最佳实践解决方案.系统已经知道第一响应者是谁,没有必要找到它. (2认同)
  • 我想给这个答案不止一个upvote.这是个天才. (2认同)

Jak*_*ger 94

这是一个类别,允许您通过调用快速找到第一个响应者[UIResponder currentFirstResponder].只需将以下两个文件添加到项目中:

UIResponder + FirstResponder.h

#import <Cocoa/Cocoa.h>
@interface UIResponder (FirstResponder)
    +(id)currentFirstResponder;
@end
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这里的诀窍是向nil发送一个动作将它发送给第一个响应者.

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

  • 苹果在 iOS 14 中阻止了在传递给“-[UIApplication sendAction:to:from:forEvent:]”的选择器中使用方法名称“findFirstResponder”。他们似乎注意到了这个答案。但您可以使用不同的方法名称来代替。 (4认同)
  • 布拉沃.这可能只是我见过Objc最美丽,最干净的黑客... (3认同)

Cod*_*der 38

这是一个基于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
    }
}
Run Code Online (Sandbox Code Playgroud)

斯威夫特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
    }
}
Run Code Online (Sandbox Code Playgroud)


can*_*boy 30

它不漂亮,但是当我不知道响应者是什么时,我辞职第一个响应者的方式:

在IB中或以编程方式创建UITextField.把它隐藏起来.如果您在IB中创建它,请将其链接到您的代码.

然后,当您想要关闭键盘时,将响应者切换到不可见的文本字段,并立即将其重新签名:

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
Run Code Online (Sandbox Code Playgroud)

  • 这既是恐怖又是天才.尼斯. (58认同)
  • 我发现有点危险 - 苹果可能有一天决定让隐藏的控件成为第一个响应者不是预期的行为并"修复"iOS不再这样做,然后你的伎俩可能会停止工作. (4认同)
  • 我宁愿认为它只是可怕......它可能会在隐藏它之前更改键盘布局(或输入视图) (3认同)
  • 或者,您可以将firstResponder分配给当前可见的UITextField,然后在该特定textField上调用resignFirstResponder.这消除了创建隐藏视图的需要.同样,+1. (2认同)

Poc*_*chi 19

对于一个Swift 3和4版本的nevyn的答案:

UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
Run Code Online (Sandbox Code Playgroud)


Sen*_*ful 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
Run Code Online (Sandbox Code Playgroud)


vil*_*ovi 7

第一个响应者可以是类UIResponder的任何实例,因此尽管有UIViews,还有其他类可能是第一个响应者.例如,UIViewController也可能是第一响应者.

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

您可以通过执行来检索第一个响应者

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

    // Do whatever you want
    [firstResponder resignFirstResponder];      
}
Run Code Online (Sandbox Code Playgroud)

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

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

可以在另一个要点中找到这种方法.

希望能帮助到你.


Jee*_*hut 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
}
Run Code Online (Sandbox Code Playgroud)

只需将其放入您的UIViewController使用中就可以这样使用:

let firstResponder = self.findFirstResponder(inView: self.view)
Run Code Online (Sandbox Code Playgroud)

请注意,结果是一个Optional值,因此如果在给定的视图子视图中找不到firstResponser,它将为nil.


Mar*_*eIV 6

我也没有遍历视图集合来查找已isFirstResponder设置的视图,而是向 发送一条消息nil,但我存储消息的接收者,以便我可以返回它并对其执行任何我希望的操作。

defer此外,我将在调用本身的语句中保存找到的响应者的选项清零。这可以确保在调用结束时不会保留任何引用(即使是较弱的引用)。

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
    }
}
Run Code Online (Sandbox Code Playgroud)

有了上面的内容,我就可以通过简单地这样做来辞职第一响应者......

UIResponder.first?.resignFirstResponder()
Run Code Online (Sandbox Code Playgroud)

但由于我的 API 实际上交还了第一响应者,所以我可以用它做任何我想做的事情。

下面的示例检查当前第一响应者是否设置了UITextField属性helpMessage,如果是,则将其显示在控件旁边的帮助气泡中。我们通过屏幕上的“快速帮助”按钮调用它。

func showQuickHelp(){

    if let textField = UIResponder?.first as? UITextField,
       let helpMessage = textField.helpMessage {
    
        textField.showHelpBubble(with:helpMessage)
    }
}
Run Code Online (Sandbox Code Playgroud)

对上述内容的支持是在扩展中定义的,UITextField就像这样......

extension UITextField {
    var helpMessage:String? { ... }
    func showHelpBubble(with message:String) { ... }
}
Run Code Online (Sandbox Code Playgroud)

现在为了支持这个功能,我们所要做的就是决定哪些文本字段有帮助消息,UI 会为我们处理剩下的事情。


Joh*_*ool 5

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


Hea*_*ers 5

Peter Steinberger 刚刚发布了关于私人通知的推文UIWindowFirstResponderDidChangeNotification,如果你想观看 firstResponder 的变化,你可以观察它。


小智 5

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

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

看起来像这样然后完成

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}
Run Code Online (Sandbox Code Playgroud)


Val*_*gin 5

只是这里的情况是令人敬畏的 Jakob Egger 方法的 Swift 版本:

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
    }

}
Run Code Online (Sandbox Code Playgroud)