私は1週間以上前に私のアプリを提出し、今日恐ろしい拒絶メールを受け取りました。非公開のAPIを使用しているため、アプリを承認できないことがわかります。具体的には、
アプリケーションに含まれている非公開のAPIはfirstResponderです。
さて、問題のあるAPI呼び出しは、実際にここでSOで見つけた解決策です。
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
現在のファーストレスポンダを画面に表示する方法アプリが拒否されないような方法を探しています。
私のアプリケーションの1つでは、ユーザーがバックグラウンドをタップした場合、最初のレスポンダに辞任させることがよくあります。この目的のために私は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;
}
Swift:
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`
}
あなたの究極の目的がファーストレスポンダを辞任することだけであれば、これでうまくいくはずです:[self.view endEditing:YES]
ファーストレスポンダを操作する一般的な方法は、ターゲットを絞ったアクションを使用しないことです。これは(最初のレスポンダから始めて)レスポンダチェーンに任意のメッセージを送信し、誰かがそのメッセージに応答するまで(セレクタに一致するメソッドを実装して)チェーンを続けていく方法です。
キーボードを消す場合、これはどのウィンドウまたはビューが最初のレスポンダであるかに関係なく動作する最も効果的な方法です。
[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
これは[self.view.window endEditing:YES]
よりも効果的です。
(このコンセプトを思い出してくれてありがとう BigZaphod )
これは[UIResponder currentFirstResponder]
を呼び出すことによってあなたがすぐに最初の応答者を見つけることを可能にするカテゴリです。次の2つのファイルをプロジェクトに追加するだけです。
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 )
これは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
}
}
それはきれいではありませんが、レスポンダが何であるかわからないときにfirstResponderを辞任する方法です。
IBまたはプログラムでUITextFieldを作成します。隠してください。 IBで作成した場合は、それを自分のコードにリンクします。
その後、キーボードを消したいときは、レスポンダを非表示のテキストフィールドに切り替えて、すぐに辞任します。
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
nevynの 回答のSwift 3および4バージョンの場合:
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
正しいファーストレスポンダを報告するソリューションがあります(他の多くのソリューションでは、たとえば、ファーストレスポンダとしてUIViewController
が報告されることはありません)。ビュー階層をループする必要はなく、プライベートAPIも使用されません。
これはAppleのメソッド sendAction:to:from:forEvent: を利用しています。これはすでにファーストレスポンダにアクセスする方法を知っています。
2つの方法でそれを微調整する必要があります。
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
ファーストレスポンダは、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
かどうかを尋ねます。
このアプローチは この他の要旨 に実装されています。
それが役に立てば幸い。
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が見つからなかった場合は、ゼロになります。
最初のレスポンダとなる可能性のあるビューを繰り返し、現在のビューかどうかを判断するために- (BOOL)isFirstResponder
を使用します。
Peter Steinbergerさんがプライベート通知UIWindowFirstResponderDidChangeNotification
について つぶやく を報告しています。これはfirstResponderの変更を監視したい場合に確認できます。
まさにそれがここにあるケースは素晴らしい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
}
}
ユーザが背景領域をタップしたときにキーボードを消す必要があるだけの場合は、ジェスチャ認識機能を追加して[[self view] endEditing:YES]
メッセージの送信に使用してはどうでしょうか。
タップジェスチャー認識機能をxibファイルまたはストーリーボードファイルに追加してアクションに関連付けることができます。
このようになり、その後終了しました
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
[[self view] endEditing:YES];
}
これは、ユーザーが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];
}
}
}
}
}
これが私の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;
}];
}
UIResponder
のカテゴリでは、UIApplication
オブジェクトに合法的に依頼して、最初のレスポンダが誰であるかを伝えることができます。
これを見なさい:
あなたもこのように試すことができます:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event {
for (id textField in self.view.subviews) {
if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) {
[textField resignFirstResponder];
}
}
}
試してみませんでしたが、良い解決策のようです
あなたはこのようにプライベートAPIを呼び出すことができます、Appleは無視します:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; SEL sel = NSSelectorFromString(@"firstResponder"); UIView *firstResponder = [keyWindow performSelector:sel];
これは再帰の候補です。 UIViewにカテゴリを追加する必要はありません。
使用方法(View Controllerから)
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;
}
Romeo https://stackoverflow.com/a/2799675/661022 による解決策はかっこいいですが、コードにもう1つループが必要であることに気付きました。私は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]);
}
}
}
}
}
}
}
私はこことはちょっと違うアプローチをしています。 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
}
私はあなたと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;
}
迅速なバージョンの @ 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
}
}
あなたはそれを得るために次のUIView
拡張子を選ぶことができます(Danielによるクレジット):
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
return subviews.first(where: {$0.firstResponder != nil })
}
}
Swift 5では、UIResponderクラスに、現在アクティブなレスポンダを取得するために使用できる「現在の」オプション属性があります。こんな感じです:
UIResponder.current?.resignFirstResponder()
更新日:私は間違っていましたUIApplication.shared.sendAction(_:to:from:for:)
を使って、このリンクに示されている最初のレスポンダを呼び出すことができます。 http://stackoverflow.com/a/14135456/74689 。
ここでの回答のほとんどは、ビュー階層にないと現在の最初のレスポンダを実際に見つけることができません。たとえば、AppDelegate
またはUIViewController
サブクラスです。
最初のレスポンダオブジェクトがUIView
ではない場合でも、確実に見つけることができる方法があります。
まず、next
のUIResponder
プロパティを使用して、その逆バージョンを実装します。
extension UIResponder {
var nextFirstResponder: UIResponder? {
return isFirstResponder ? self : next?.nextFirstResponder
}
}
この計算されたプロパティで、たとえUIView
でなくても、現在の最初のレスポンダを下から上に見つけることができます。たとえば、View Controllerが最初のレスポンダの場合は、view
から管理しているUIViewController
までです。
ただし、現在のファーストレスポンダを取得するには、トップダウンの解決策、つまりvar
が1つ必要です。
まずビュー階層について
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
}
}
したがって、最初のレスポンダを取得したいときは、次のように呼び出すことができます。
let firstResponder = UIResponder.first
以下のコードは動作します。
- (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;
}