无需使用私有 API 即可获取当前第一响应者
我在一个多星期前提交了我的应用程序,今天收到了可怕的拒绝电子邮件。它告诉我,我的应用程序无法被接受,因为我使用的是非公共 API;具体来说,它说,
您的应用程序中包含的非公共 API 是firstResponder。
现在,有问题的 API 调用实际上是我在 SO:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
How do I get the currentfirst responder on the screen? 上找到的解决方案。我正在寻找一种不会让我的应用程序被拒绝的方法。
I submitted my app a little over a week ago and got the dreaded rejection email today. It tells me that my app cannot be accepted because I'm using a non-public API; specifically, it says,
The non-public API that is included in your application is firstResponder.
Now, the offending API call is actually a solution I found here on SO:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
How do I get the current first responder on the screen? I'm looking for a way that won't get my app rejected.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(26)
这就是我在用户单击 ModalViewController 中的“保存/取消”时查找 UITextField 是第一个响应者所做的事情:
This is what I did to find what UITextField is the firstResponder when the user clicks Save/Cancel in a ModalViewController:
这就是我的 UIViewController 类别中的内容。对很多事情都很有用,包括获得急救人员。积木很棒!
This is what I have in my UIViewController Category. Useful for many things, including getting first responder. Blocks are great!
您也可以这样尝试:
我没有尝试过,但这似乎是一个很好的解决方案
You can try also like this:
I didn't try it but it seems a good solution
这是递归的良好候选者!无需向 UIView 添加类别。
用法(从您的视图控制器):
代码:
This is good candidate for recursion! No need to add a category to UIView.
Usage (from your view controller):
Code:
@thomas-müller< 的 Swift 版本/a> 的回应
Swift version of @thomas-müller's response
更新:我错了。您确实可以使用
UIApplication.shared.sendAction(_:to:from:for:)
来调用此链接中演示的第一响应者:http://stackoverflow.com/a/14135456/746890。如果当前的第一响应者不在视图层次结构中,这里的大多数答案都无法真正找到它。例如,
AppDelegate
或UIViewController
子类。即使第一响应者对象不是 UIView,也有一种方法可以保证您找到它。
首先让我们使用
UIResponder
的next
属性来实现它的反向版本:通过这个计算属性,我们可以从下到上找到当前的第一响应者,即使它不是
UIView
。例如,从view
到管理它的UIViewController
(如果视图控制器是第一响应者)。然而,我们仍然需要一个自上而下的解析,一个
var
来获取当前的第一响应者。首先是视图层次结构:
这将向后搜索第一个响应者,如果找不到它,它会告诉它的子视图做同样的事情(因为它的子视图的
next
不一定是它自己)。这样我们就可以从任何视图中找到它,包括UIWindow
。最后,我们可以构建这个:
所以当你想检索第一响应者时,你可以调用:
Update: I was wrong. You can indeed use
UIApplication.shared.sendAction(_:to:from:for:)
to call the first responder demonstrated in this link: http://stackoverflow.com/a/14135456/746890.Most of the answers here can't really find the current first responder if it is not in the view hierarchy. For example,
AppDelegate
orUIViewController
subclasses.There is a way to guarantee you to find it even if the first responder object is not a
UIView
.First lets implement a reversed version of it, using the
next
property ofUIResponder
:With this computed property, we can find the current first responder from bottom to top even if it's not
UIView
. For example, from aview
to theUIViewController
who's managing it, if the view controller is the first responder.However, we still need a top-down resolution, a single
var
to get the current first responder.First with the view hierarchy:
This will search for the first responder backwards, and if it couldn't find it, it would tell its subviews to do the same thing (because its subview's
next
is not necessarily itself). With this we can find it from any view, includingUIWindow
.And finally, we can build this:
So when you want to retrieve the first responder, you can call:
您可以选择以下
UIView
扩展来获取它(由 Daniel 提供):You can choose the following
UIView
extension to get it (credit by Daniel):你可以像这样调用私有API,苹果忽略:
you can call privite api like this ,apple ignore:
我想与您分享我在 UIView 的任何位置查找第一响应者的实现。我希望它有帮助,并对我的英语感到抱歉。谢谢
I would like to shared with you my implementation for find first responder in anywhere of UIView. I hope it helps and sorry for my english. Thanks
romeo https://stackoverflow.com/a/2799675/661022 的解决方案很酷,但我注意到代码还需要一个循环。我正在使用 tableViewController。
我编辑了脚本然后检查了。一切都很完美。
我建议尝试这个:
The solution from romeo https://stackoverflow.com/a/2799675/661022 is cool, but I noticed that the code needs one more loop. I was working with tableViewController.
I edited the script and then I checked. Everything worked perfect.
I recommed to try this:
下面的代码有效。
Code below work.
如果您的最终目标只是辞职第一响应者,那么这应该有效:
[self.view endEditing:YES]
If your ultimate aim is just to resign the first responder, this should work:
[self.view endEditing:YES]
在我的一个应用程序中,如果用户点击背景,我经常希望第一响应者辞职。为此,我在 UIView 上编写了一个类别,我在 UIWindow 上调用了该类别。
以下内容基于此,并且应该返回第一响应者。
iOS 7+
Swift:
Swift 中的使用示例:
In one of my applications I often want the first responder to resign if the user taps on the background. For this purpose I wrote a category on UIView, which I call on the UIWindow.
The following is based on that and should return the first responder.
iOS 7+
Swift:
Usage example in Swift:
操纵第一响应者的常见方法是使用零目标操作。这是一种将任意消息发送到响应者链(从第一个响应者开始),并沿着链继续向下,直到有人响应该消息(已实现与选择器匹配的方法)的方法。
对于关闭键盘的情况,无论哪个窗口或视图是第一响应者,这都是最有效的方法:
这应该比
[self.view.window endEditing:YES]
。(感谢 BigZaphod 提醒我这个概念)
A common way of manipulating the first responder is to use nil targeted actions. This is a way of sending an arbitrary message to the responder chain (starting with the first responder), and continuing down the chain until someone responds to the message (has implemented a method matching the selector).
For the case of dismissing the keyboard, this is the most effective way that will work no matter which window or view is first responder:
This should be more effective than even
[self.view.window endEditing:YES]
.(Thanks to BigZaphod for reminding me of the concept)
这里有一个类别,允许您通过调用
[UIResponder currentFirstResponder]
来快速找到第一响应者。只需将以下两个文件添加到您的项目中即可:UIResponder+FirstResponder.h:
UIResponder+FirstResponder.m:
这里的技巧是将操作发送到 nil 将其发送到第一响应者。
(我最初在这里发布了这个答案:https://stackoverflow.com/a/14135456/322427)
Here's a category that allows you to quickly find the first responder by calling
[UIResponder currentFirstResponder]
. Just add the following two files to your project:UIResponder+FirstResponder.h:
UIResponder+FirstResponder.m:
The trick here is that sending an action to nil sends it to the first responder.
(I originally published this answer here: https://stackoverflow.com/a/14135456/322427)
这是基于 Jakob Egger 最优秀的答案在 Swift 中实现的扩展:
Swift 4
Here is a Extension implemented in Swift based on Jakob Egger's most excellent answer:
Swift 4
这并不漂亮,但是当我不知道响应者是什么时,我辞职的方式是:
创建一个 UITextField,无论是在 IB 中还是以编程方式。使其隐藏。如果您是在 IB 中制作的,请将其链接到您的代码。
然后,当您想要关闭键盘时,将响应者切换到不可见的文本字段,并立即退出它:
It's not pretty, but the way I resign the firstResponder when I don't know what that the responder is:
Create an UITextField, either in IB or programmatically. Make it Hidden. Link it up to your code if you made it in IB.
Then, when you want to dismiss the keyboard, you switch the responder to the invisible text field, and immediately resign it:
对于 Swift 3 和nevyn 答案的 4 版:
For a Swift 3 & 4 version of nevyn's answer:
这是一个报告正确的第一响应者的解决方案(例如,许多其他解决方案不会将
UIViewController
报告为第一响应者),不需要在视图层次结构上循环,并且不使用私有 API。它利用Apple的方法 sendAction:to:from:forEvent:,它已经知道如何访问第一响应者。
我们只需要通过两种方式调整它:
UIResponder
,以便它可以在第一响应者上执行我们自己的代码。UIEvent
以返回第一个响应者。这是代码:
Here's a solution which reports the correct first responder (many other solutions won't report a
UIViewController
as the first responder, for example), doesn't require looping over the view hierarchy, and doesn't use private APIs.It leverages Apple's method sendAction:to:from:forEvent:, which already knows how to access the first responder.
We just need to tweak it in 2 ways:
UIResponder
so it can execute our own code on the first responder.UIEvent
in order to return the first responder.Here is the code:
使用Swift和特定的
UIView
对象这可能会有所帮助:只需将它放在你的
UIViewController
中并像这样使用它this:请注意,结果是一个可选值,因此如果在给定视图子视图层次结构中找不到firstResponder,则结果将为nil。
Using Swift and with a specific
UIView
object this might help:Just place it in your
UIViewController
and use it like this:Take note that the result is an Optional value so it will be nil in case no firstResponder was found in the given views subview hierarchy.
第一响应者可以是 UIResponder 类的任何实例,因此尽管有 UIView,但还有其他类可能是第一响应者。例如,
UIViewController
也可能是第一响应者。在 this gist 中,您将找到一种递归方法,通过循环遍历控制器的层次结构来获取第一个响应者从应用程序窗口的 rootViewController 开始。
您可以通过执行以下操作来检索第一响应者
但是,如果第一响应者不是 UIView 或 UIViewController 的子类,则此方法将失败。
为了解决这个问题,我们可以采用不同的方法,在
UIResponder
上创建一个类别,并执行一些神奇的调整,以便能够构建该类的所有活动实例的数组。然后,为了获得第一响应者,我们可以简单地迭代并询问每个对象是否-isFirstResponder
。这种方法可以在这个其他要点中找到实现。
希望有帮助。
The first responder can be any instance of the class UIResponder, so there are other classes that might be the first responder despite the UIViews. For example
UIViewController
might also be the first responder.In this gist you will find a recursive way to get the first responder by looping through the hierarchy of controllers starting from the rootViewController of the application's windows.
You can retrieve then the first responder by doing
However, if the first responder is not a subclass of UIView or UIViewController, this approach will fail.
To fix this problem we can do a different approach by creating a category on
UIResponder
and perform some magic swizzeling to be able to build an array of all living instances of this class. Then, to get the first responder we can simple iterate and ask each object if-isFirstResponder
.This approach can be found implemented in this other gist.
Hope it helps.
迭代可能是第一响应者的视图,并使用
- (BOOL)isFirstResponder
来确定它们当前是否是。Iterate over the views that could be the first responder and use
- (BOOL)isFirstResponder
to determine if they currently are.我也向
nil
发送一条消息,而不是遍历视图集合来查找设置了isFirstResponder
的视图,但我存储了消息的接收者,以便我可以返回它并用它做我想做的事。此外,我将调用本身中的
defer
语句中保存找到的响应者的可选值清零。这可以确保在调用结束时不会保留任何引用(即使是较弱的引用)。通过上面的内容,我可以通过简单地执行此操作来辞职第一响应者...
但是由于我的 API 实际上交还了第一响应者,所以我可以用它做任何我想做的事情。
下面的示例检查当前第一响应者是否是设置了
helpMessage
属性的UITextField
,如果是,则将其显示在控件旁边的帮助气泡中。我们通过屏幕上的“快速帮助”按钮调用它。对上述内容的支持是在
UITextField
的扩展中定义的,如下所示...现在要支持此功能,我们所要做的就是确定哪些文本字段有帮助消息,并且 UI 负责处理为我们休息。
Rather than iterate through the collection of views looking for the one that has
isFirstResponder
set, I too send a message tonil
, but I store the receiver of the message so I can return it and do whatever I wish with it.Additionally, I zero out the optional that holds the found responder in a
defer
statement from within the call itself. This ensures no references remain--even weak ones--at the end of the call.With the above, I can resign the first responder by simply doing this...
But since my API actually hands back whatever the first responder is, I can do whatever I want with it.
Here's an example that checks if the current first responder is a
UITextField
with ahelpMessage
property set, and if so, shows it in a help bubble right next to the control. We call this from a 'Quick Help' button on our screen.The support for the above is defined in an extension on
UITextField
like so...Now to support this feature, all we have to do is decide which text fields have help messages and the UI takes care of the rest for us.
Peter Steinberger 刚刚推文关于私人通知
UIWindowFirstResponderDidChangeNotification
,您可以观察到如果你想观看firstResponder的变化。Peter Steinberger just tweeted about the private notification
UIWindowFirstResponderDidChangeNotification
, which you can observe if you want to watch the firstResponder change.如果您只需要在用户点击背景区域时关闭键盘,为什么不添加手势识别器并使用它来发送
[[self view] endEditing:YES]
消息?您可以在 xib 或 Storyboard 文件中添加点击手势识别器并将其连接到一个动作,
看起来像这样然后完成
If you just need to kill the keyboard when the user taps on a background area why not add a gesture recognizer and use it to send the
[[self view] endEditing:YES]
message?you can add the Tap gesture recogniser in the xib or storyboard file and connect it to an action,
looks something like this then finished
这就是 Jakob Egger 的方法的 Swift 版本:
Just it case here is Swift version of awesome Jakob Egger's approach: