355

私は1週間ほど前にアプリを送信し、今日は恐ろしい拒否メールを受け取りました。非公開のAPIを使用しているため、アプリを受け入れられないことがわかります。具体的には、

アプリケーションに含まれている非公開APIはfirstResponderです。

さて、問題のあるAPI呼び出しは、実際には私がここSOで見つけた解決策です。

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

画面に現在のファーストレスポンダーを表示するにはどうすればよいですか?アプリが拒否されない方法を探しています。

4

28 に答える 28

534

あなたの最終的な目的がファーストレスポンダーを辞任することだけである場合、これはうまくいくはずです:[self.view endEditing:YES]

于 2010-05-22T10:13:16.140 に答える
354

私のアプリケーションの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

iOS7以降

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

迅速:

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`
}
于 2009-12-01T00:43:38.890 に答える
193

ファーストレスポンダーを操作する一般的な方法は、nilターゲットアクションを使用することです。これは、任意のメッセージをレスポンダーチェーン(最初のレスポンダーから開始)に送信し、誰かがメッセージに応答するまで(セレクターに一致するメソッドを実装している)チェーンをたどる方法です。

キーボードを閉じる場合、これは、どのウィンドウまたはビューがファーストレスポンダーであるかに関係なく機能する最も効果的な方法です。

[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];

これは、よりも効果的であるはず[self.view.window endEditing:YES]です。

(コンセプトを思い出させてくれたBigZaphodに感謝します)

于 2012-08-01T22:05:57.080 に答える
106

これは、を呼び出すことでファーストレスポンダーをすばやく見つけることができるカテゴリです[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 )

于 2014-01-24T10:54:48.570 に答える
48

これは、JakobEggerの最も優れた回答に基づいて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
    }
}
于 2014-11-26T03:20:37.593 に答える
29

きれいではありませんが、レスポンダーが何であるかわからないときにfirstResponderを辞任する方法は次のとおりです。

IBまたはプログラムでUITextFieldを作成します。隠してください。IBで作成した場合は、コードにリンクします。

次に、キーボードを閉じたい場合は、レスポンダーを非表示のテキストフィールドに切り替え、すぐに辞任します。

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
于 2010-05-24T18:33:29.113 に答える
21

nevynの答えのSwift3 &4バージョンの場合:

UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
于 2017-01-18T08:35:36.853 に答える
11

これは、正しいファーストレスポンダーを報告するソリューションです(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
于 2014-07-08T18:46:25.553 に答える
8

ファーストレスポンダーはクラスUIResponderの任意のインスタンスである可能性があるため、UIViewsにもかかわらずファーストレスポンダーになる可能性のある他のクラスがあります。たとえばUIViewController、ファーストレスポンダーになることもあります。

この要点では、アプリケーションのウィンドウのrootViewControllerから開始するコントローラーの階層をループすることにより、最初のレスポンダーを取得する再帰的な方法を見つけることができます。

次に、次のようにしてファーストレスポンダーを取得できます。

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

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

ただし、ファーストレスポンダーがUIViewまたはUIViewControllerのサブクラスでない場合、このアプローチは失敗します。

この問題を修正するには、カテゴリを作成し、UIResponder魔法のスウィズリングを実行して、このクラスのすべての生きているインスタンスの配列を構築できるようにすることで、別のアプローチを実行できます。次に、最初のレスポンダーを取得するために、単純に反復して、各オブジェクトに-isFirstResponder

このアプローチは、この他の要点に実装されていることがわかります。

それが役に立てば幸い。

于 2012-05-11T16:06:42.303 に答える
8

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)

結果はオプションの値であるため、指定されたビューのサブビュー階層でfirstResponderが見つからなかった場合はnilになることに注意してください。

于 2015-05-05T16:29:33.080 に答える
6

ファーストレスポンダーになる可能性のあるビューを繰り返し、現在のビュー- (BOOL)isFirstResponderを判別するために使用します。

于 2009-12-01T00:37:04.627 に答える
5

Peter Steinbergerがプライベート通知についてツイートしUIWindowFirstResponderDidChangeNotificationました。これは、firstResponderの変更を確認したい場合に確認できます。

于 2013-05-07T14:55:33.227 に答える
5

ユーザーが背景領域をタップしたときにキーボードを強制終了する必要がある場合は、ジェスチャ認識機能を追加し、それを使用して[[self view] endEditing:YES]メッセージを送信してみませんか?

タップジェスチャレコグナイザーをxibまたはストーリーボードファイルに追加して、アクションに接続できます。

このように見えて完成

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}
于 2013-06-27T11:28:30.250 に答える
5

ここでのケースは、素晴らしいJakobEggerのアプローチの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
    }

}
于 2016-05-27T21:13:03.630 に答える
5

私は他の人とは少し違うアプローチをしています。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 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
    }

    @objc func storeFirstResponder(_ sender: AnyObject) {

        // Capture the recipient of this message (self), which is the first responder
        _foundFirstResponder = self
    }
}

以上で、これを行うだけでファーストレスポンダーを辞任することができます...

UIResponder.first?.resignFirstResponder()

しかし、私のAPIは実際には最初のレスポンダーが何であれ返してくれるので、私はそれを使ってやりたいことが何でもできます。

これは、現在のファーストレスポンダーがプロパティが設定されているかどうかを確認し、設定されている場合は、コントロールのすぐ横にあるヘルプバブルに表示する例UITextFieldですhelpMessage。これは、画面の[クイックヘルプ]ボタンから呼び出します。

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が処理してくれます。

于 2018-10-15T19:45:59.513 に答える
4

これは、ユーザーがModalViewControllerで[保存/キャンセル]をクリックしたときに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];
                }

            }
        }

    }

}
于 2010-05-10T00:08:55.700 に答える
3

これは、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;
 }];
}
于 2012-01-24T05:32:09.643 に答える
3

カテゴリをオンにすると、オブジェクトに最初の応答者が誰であるかUIResponderを合法的に尋ねることができます。UIApplication

これを参照してください:

どの子がファーストレスポンダーのステータスを持っているかをiOSビューに尋ねる方法はありますか?

于 2012-04-29T14:43:21.457 に答える
3

次のUIView拡張子を選択して取得できます(Danielによるクレジット)

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }
        return subviews.first(where: {$0.firstResponder != nil })
    }
}
于 2019-05-17T08:27:24.653 に答える
2

次のようにすることもできます。

- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event { 

    for (id textField in self.view.subviews) {

        if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) {
            [textField resignFirstResponder];
        }
    }
} 

私はそれを試しませんでしたが、それは良い解決策のようです

于 2011-09-01T09:44:53.740 に答える
2

これは再帰の良い候補です!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;
}
于 2013-10-10T23:54:46.480 に答える
2

あなたはこのようにpriviteapiを呼び出すことができます、appleは無視します:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];
于 2014-11-11T10:11:27.720 に答える
2

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;
}
于 2015-09-22T12:21:56.963 に答える
2

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

}
于 2016-04-04T15:15:39.580 に答える
1

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]);
                        }
                    }
                }
            }
        }
    }
}
于 2014-05-29T11:43:48.247 に答える
1

更新:私は間違っていました。あなたは確かUIApplication.shared.sendAction(_:to:from:for:)にこのリンクで示されている最初のレスポンダーを呼び出すために使用することができます:http ://stackoverflow.com/a/14135456/746890 。


ここでの回答のほとんどは、ビュー階層にない場合、現在のファーストレスポンダーを実際に見つけることができません。たとえば、AppDelegateまたはUIViewControllerサブクラス。

ファーストレスポンダーオブジェクトがでなくても、それを見つけることを保証する方法がありますUIView

まず、 :のnextプロパティを使用して、その逆バージョンを実装しましょう。UIResponder

extension UIResponder {
    var nextFirstResponder: UIResponder? {
        return isFirstResponder ? self : next?.nextFirstResponder
    }
}

この計算されたプロパティを使用すると、そうでない場合でも、現在のファーストレスポンダーを下から上に見つけることができますUIView。たとえば、ビューコントローラが最初のレスポンダーである場合、それを管理している人viewまで。UIViewController

varただし、現在のファーストレスポンダーを取得するには、トップダウンの解決策が必要です。

最初にビュー階層を使用します。

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
于 2018-05-22T16:17:06.690 に答える
0

以下のコードが機能します。

- (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;
}   
于 2016-11-15T13:27:41.057 に答える
0

ファーストレスポンダーを見つける最も簡単な方法:

func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool

デフォルトの実装では、アクションメソッドを指定されたターゲットオブジェクトにディスパッチします。ターゲットが指定されていない場合は、最初のレスポンダーにディスパッチします。

次のステップ:

extension UIResponder
{
    private weak static var first: UIResponder? = nil

    @objc
    private func firstResponderWhereYouAre(sender: AnyObject)
    {
        UIResponder.first = self
    }

    static var actualFirst: UIResponder?
    {
        UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
        return UIResponder.first
    }
}

使用法:UIResponder.actualFirstあなた自身の目的のためだけに入手してください。

于 2019-10-14T16:49:02.063 に答える