Obtenez le premier répondeur actuel sans utiliser une API privée

J’ai soumis mon application il y a un peu plus d’une semaine et j’ai reçu le mail de rejet redouté aujourd’hui. Il me dit que mon application ne peut pas être acceptée car j’utilise une API non publique. spécifiquement, il dit,

L’API non publique incluse dans votre application est firstResponder.

Maintenant, l’appel API en cause est en fait une solution que j’ai trouvée ici sur SO:

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

Comment puis-je obtenir le premier répondant actuel à l’écran? Je cherche un moyen pour que mon application ne soit pas rejetée.

Dans l’une de mes applications, je souhaite souvent que le premier intervenant démissionne si l’utilisateur appuie sur l’arrière-plan. A cette fin, j’ai écrit une catégorie sur UIView, que j’appelle sur l’UIWindow.

Ce qui suit est basé sur cela et devrait retourner le premier répondant.

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

Rapide:

 extension UIView { var firstResponder: UIView? { guard !isFirstResponder else { return self } for subview in subviews { if let firstResponder = subview.firstResponder { return firstResponder } } return nil } } 

Exemple d’utilisation dans Swift:

 if let firstResponder = view.window?.firstResponder { // do something with `firstResponder` } 

Si votre but ultime est juste de renoncer au premier intervenant, cela devrait fonctionner: [self.view endEditing:YES]

Une manière courante de manipuler le premier intervenant consiste à utiliser des actions ciblées nulles. C’est un moyen d’envoyer un message arbitraire à la chaîne de répondeur (en commençant par le premier répondant) et de continuer jusqu’à ce que quelqu’un réponde au message (a implémenté une méthode correspondant au sélecteur).

Dans le cas du rejet du clavier, c’est le moyen le plus efficace qui fonctionnera, quelle que soit la fenêtre ou la vue à laquelle le premier répondeur:

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

Cela devrait être plus efficace que même [self.view.window endEditing:YES] .

(Merci à BigZaphod de me rappeler le concept)

Voici une catégorie qui vous permet de trouver rapidement le premier répondant en appelant [UIResponder currentFirstResponder] . Ajoutez simplement les deux fichiers suivants à votre projet:

UIResponder + FirstResponder.h

 #import  @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 

L’astuce ici est que l’envoi d’une action à zéro l’envoie au premier répondant.

(J’ai initialement publié cette réponse ici: https://stackoverflow.com/a/14135456/322427 )

Voici une extension implémentée dans Swift basée sur la meilleure réponse de Jakob Egger:

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

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

Ce n’est pas joli, mais la façon dont je démissionne le premier répondeur alors que je ne sais pas ce que le répondeur est:

Créez un object UITextField, soit dans IB, soit par programme. Rendez-le caché. Reliez-le à votre code si vous l’avez fait dans IB.

Ensuite, lorsque vous souhaitez supprimer le clavier, vous basculez le répondeur sur le champ de texte invisible et vous le démettez immédiatement:

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

Voici une solution qui signale le premier répondeur correct (de nombreuses autres solutions ne signaleront pas un UIViewController comme le premier répondant, par exemple), ne nécessite pas de bouclage sur la hiérarchie des vues et n’utilisent pas d’API privées.

Il s’appuie sur la méthode sendAction d’ Apple : to: from: forEvent: qui sait déjà comment accéder au premier répondeur.

Nous avons juste besoin de le modifier de deux manières:

  • Étendez UIResponder pour qu’il puisse exécuter notre propre code sur le premier répondeur.
  • Sous-classe UIEvent afin de renvoyer le premier répondant.

Voici le code:

 @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 

Pour une version Swift 3 de la réponse de nevyn :

 UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil) 

Le premier répondant peut être n’importe quelle instance de la classe UIResponder, il y a donc d’autres classes qui pourraient être le premier à répondre en dépit des UIViews. Par exemple, UIViewController pourrait également être le premier intervenant.

Dans cet aperçu, vous trouverez une manière récursive d’obtenir le premier répondant en effectuant une boucle dans la hiérarchie des contrôleurs à partir du rootViewController des fenêtres de l’application.

Vous pouvez récupérer le premier répondant en faisant

 - (void)foo { // Get the first responder id firstResponder = [UIResponder firstResponder]; // Do whatever you want [firstResponder resignFirstResponder]; } 

Toutefois, si le premier répondeur n’est pas une sous-classe de UIView ou UIViewController, cette approche échouera.

Pour résoudre ce problème, nous pouvons adopter une approche différente en créant une catégorie sur UIResponder et effectuer un swizzeling magique pour pouvoir construire un tableau de toutes les instances vivantes de cette classe. Ensuite, pour obtenir le premier répondant, nous pouvons effectuer une itération simple et demander à chaque object si -isFirstResponder .

Cette approche peut être trouvée mise en œuvre dans cet autre esprit .

J’espère que cela aide.

En utilisant Swift et avec un object UIView spécifique, cela pourrait aider:

 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 } 

Il suffit de le placer dans votre UIViewController et de l’utiliser comme ceci:

 let firstResponder = self.findFirstResponder(inView: self.view) 

Notez que le résultat est une valeur facultative , elle sera donc nulle si aucun firstResponser n’a été trouvé dans les sous-vues vues.

Parcourez les vues qui pourraient être le premier répondant et utilisez - (BOOL)isFirstResponder pour déterminer si elles le sont actuellement.

Peter Steinberger a juste tweeté sur la notification privée UIWindowFirstResponderDidChangeNotification , que vous pouvez observer si vous voulez voir le premier changement de répondeur.

Si vous avez juste besoin de tuer le clavier lorsque l’utilisateur tape sur une zone d’arrière-plan, pourquoi ne pas append un identificateur de geste et l’utiliser pour envoyer le message [[self view] endEditing:YES] ?

vous pouvez append le détecteur de mouvements Tap dans le fichier xib ou storyboard et le connecter à une action,

ressemble à quelque chose comme ça alors fini

 - (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{ [[self view] endEditing:YES]; } 

C’est ce que j’ai fait pour trouver ce que UITextField est le premier répondeur lorsque l’utilisateur clique sur Enregistrer / Annuler dans un ModalViewController:

  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]; } } } } } 

Juste le cas, voici la version rapide de l’approche géniale de Jakob Egger:

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

C’est ce que j’ai dans ma catégorie UIViewController. Utile pour beaucoup de choses, y compris obtenir le premier intervenant. Les blocs sont super!

 - (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; }]; } 

Avec une catégorie sur UIResponder , il est possible de demander légalement à l’object UIApplication de vous indiquer qui est le premier répondant.

Regarde ça:

Existe-t-il un moyen de demander à une vue iOS quel est le statut de premier répondant de ses enfants?

Vous pouvez essayer aussi comme ça:

 - (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event { for (id textField in self.view.subviews) { if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) { [textField resignFirstResponder]; } } } 

Je n’ai pas essayé mais cela semble une bonne solution

La solution de Romeo https://stackoverflow.com/a/2799675/661022 est géniale, mais j’ai remarqué que le code nécessitait une boucle de plus. Je travaillais avec tableViewController. J’ai édité le script puis j’ai vérifié. Tout fonctionnait parfaitement.

J’ai recommandé d’essayer ceci:

 - (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]); } } } } } } } 

C’est un bon candidat pour la récursivité! Pas besoin d’append une catégorie à UIView.

Utilisation (depuis votre contrôleur de vue):

 UIView *firstResponder = [self findFirstResponder:[self view]]; 

Code:

 // 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; } 

vous pouvez appeler un tel api privé, apple ignore:

 UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; SEL sel = NSSelectorFromSsortingng(@"firstResponder"); UIView *firstResponder = [keyWindow performSelector:sel]; 

Je voudrais partager avec vous mon implémentation pour trouver le premier répondeur partout dans UIView. J’espère que ça aide et désolé pour mon anglais. Merci

 + (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; 

}

Version rapide de la réponse de @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 } } 

Code ci-dessous travail.

 - (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; } 

Mise à jour: j’avais tort. Vous pouvez en effet utiliser UIApplication.shared.sendAction(_:to:from:for:) pour appeler le premier répondant démontré dans ce lien: http://stackoverflow.com/a/14135456/746890 .


La plupart des réponses ici ne permettent pas de trouver le premier répondeur actuel s’il ne se trouve pas dans la hiérarchie des vues. Par exemple, les sous-classes AppDelegate ou UIViewController .

Il existe un moyen de vous garantir de le trouver même si le premier object répondeur n’est pas un UIView .

UIResponder abord une version inversée de celle-ci, en utilisant la propriété next de UIResponder :

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

Avec cette propriété calculée, nous pouvons trouver le premier répondant actuel de bas en haut, même si ce n’est pas UIView . Par exemple, d’une view à l’ UIViewController qui la gère, si le contrôleur de vue est le premier répondant.

Cependant, nous avons toujours besoin d’une résolution descendante, d’une seule var pour obtenir le premier répondant actuel.

Tout d’abord avec la hiérarchie des vues:

 extension UIView { var previousFirstResponder: UIResponder? { return nextFirstResponder ?? subviews.compactMap { $0.previousFirstResponder }.first } } 

Cela recherchera le premier répondant en arrière et s’il ne pouvait pas le trouver, il indiquerait à ses sous-vues de faire la même chose (parce que la sous-vue next n’est pas nécessairement elle-même). Avec cela, nous pouvons le trouver depuis n’importe quelle vue, y compris UIWindow .

Et enfin, nous pouvons construire ceci:

 extension UIResponder { static var first: UIResponder? { return UIApplication.shared.windows.compactMap({ $0.previousFirstResponder }).first } } 

Ainsi, lorsque vous souhaitez récupérer le premier répondant, vous pouvez appeler:

 let firstResponder = UIResponder.first