J'ai soumis mon application il y a un peu plus d'une semaine et j'ai reçu l'e-mail de rejet tant redouté aujourd'hui. Cela 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 d'API incriminé 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 faire pour que le premier répondant actuel soit affiché à l'écran? Je cherche un moyen de ne pas rejeter mon application.
Dans l'une de mes applications, je souhaite souvent que le premier répondant démissionne si l'utilisateur tape sur l'arrière-plan. À cette fin, j'ai écrit une catégorie sur UIView, que j'appelle sur UIWindow.
Ce qui suit est basé sur cela et devrait renvoyer 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;
}
Swift:
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 objectif ultime est simplement de démissionner du premier répondant, cela devrait fonctionner: [self.view endEditing:YES]
Une façon courante de manipuler le premier répondant consiste à utiliser des actions nulles ciblées. C'est un moyen d'envoyer un message arbitraire à la chaîne de répondeurs (en commençant par le premier répondant) et de poursuivre la chaîne jusqu'à ce que quelqu'un réponde au message (a mis en œuvre une méthode correspondant au sélecteur).
Pour le cas de la suppression du clavier, c'est le moyen le plus efficace qui fonctionne quelle que soit la fenêtre ou la vue qui est le premier intervenant:
[[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 intervenant en appelant [UIResponder currentFirstResponder]
. Ajoutez simplement les deux fichiers suivants à votre projet:
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
L'astuce ici est que l'envoi d'une action à nil 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 l'excellente 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
}
}
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 j'ai démissionné du premier répondeur quand je ne sais pas ce qu'est le répondeur:
Créez un UITextField, dans IB ou par programme. Faites le caché. Associez-le à votre code si vous l'avez créé dans IB.
Ensuite, lorsque vous souhaitez ignorer le clavier, vous basculez le répondeur sur le champ de texte invisible et vous le résiliez immédiatement:
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
Pour une Swift version 3 & 4 de nevyn's réponse:
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
Voici une solution qui indique le premier répondant correct (de nombreuses autres solutions ne signalent pas un UIViewController
comme premier répondant, par exemple), ne nécessitent pas de boucle sur la hiérarchie des vues et n'utilisent pas d'API privées.
Il exploite la méthode d'Apple sendAction: to: from: forEvent: , qui sait déjà comment accéder au premier répondant.
Nous avons juste besoin de le modifier de deux manières:
UIResponder
pour qu'il puisse exécuter notre propre code sur le premier répondant.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
Le premier répondant peut être n'importe quelle instance de la classe UIResponder. Il existe donc d'autres classes pouvant être le premier à répondre malgré les vues UIV. Par exemple, UIViewController
pourrait également être le premier intervenant.
Dans this Gist , vous trouverez un moyen récursif d'obtenir le premier répondant en parcourant la hiérarchie des contrôleurs à partir du rootViewController des fenêtres de l'application.
Vous pouvez alors 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];
}
Cependant, si le premier répondant n'est pas une sous-classe de UIView ou UIViewController, cette approche échouera.
Pour résoudre ce problème, nous pouvons faire une approche différente en créant une catégorie sur UIResponder
et en effectuant quelques opérations magiques pour pouvoir créer un tableau de toutes les instances vivantes de cette classe. Ensuite, pour obtenir le premier intervenant, nous pouvons simplement effectuer une itération et demander à chaque objet si -isFirstResponder
.
Cette approche peut être trouvée implémentée dans cet autre Gist .
J'espère que ça aide.
Avec Swift et avec un objet 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
}
Placez-le simplement dans votre UIViewController
et utilisez-le comme ceci:
let firstResponder = self.findFirstResponder(inView: self.view)
Prenez note que le résultat est un valeur facultative, donc il sera nul si aucune firstResponser n'a été trouvée dans les vues sous-vues données.
Parcourez les vues qui pourraient être le premier intervenant et utilisez - (BOOL)isFirstResponder
pour déterminer si elles le sont actuellement.
Peter Steinberger vient de tweeted à propos de la notification privée UIWindowFirstResponderDidChangeNotification
, que vous pouvez observer si vous souhaitez regarder le premier répondeur changer.
Juste le cas ici est la version Swift de l’impressionnant approche 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
}
}
Si vous avez juste besoin de tuer le clavier lorsque l'utilisateur appuie sur une zone d'arrière-plan, pourquoi ne pas ajouter un identificateur de mouvements et l'utiliser pour envoyer le message [[self view] endEditing:YES]
?
vous pouvez ajouter le dispositif de reconnaissance de gestes Taper dans le fichier xib ou storyboard et le connecter à une action,
ressemble à ceci puis fini
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
[[self view] endEditing:YES];
}
C'est ce que j'ai fait pour trouver quel 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];
}
}
}
}
}
C'est ce que j'ai dans ma catégorie UIViewController. Utile pour beaucoup de choses, y compris pour obtenir le premier répondant. 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'objet UIApplication
de vous dire qui est le premier répondant.
Regarde ça:
Y a-t-il un moyen de demander à une vue iOS lequel de ses enfants a le statut de premier répondant?
Vous pouvez choisir l’extension suivante UIView
pour l’obtenir (crédit de Daniel):
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
return subviews.first(where: {$0.firstResponder != nil })
}
}
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 ne l'ai pas essayé mais cela me semble une bonne solution
vous pouvez appeler une API privée comme ceci, Apple ignore:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; SEL sel = NSSelectorFromString(@"firstResponder"); UIView *firstResponder = [keyWindow performSelector:sel];
C'est un bon candidat pour la récursion! Pas besoin d'ajouter une catégorie à UIView.
Utilisation (à partir de 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;
}
La solution de romeo https://stackoverflow.com/a/2799675/661022 est cool, mais j'ai remarqué que le code nécessite une boucle de plus. Je travaillais avec tableViewController. J'ai édité le script et ensuite j'ai vérifié. Tout a fonctionné 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]);
}
}
}
}
}
}
}
J'ai une approche légèrement différente de la plupart des gens ici. Plutôt que de parcourir la collection de vues à la recherche de celle qui a isFirstResponder
définie, j'envoie aussi un message à nil
, mais je stocke le récepteur (le cas échéant), puis renvoie cette valeur pour que l'appelant obtient l'instance réelle (encore une fois, le cas échéant).
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
}
}
Je peux alors démissionner du premier intervenant, le cas échéant, en procédant ainsi ...
return UIResponder.first?.resignFirstResponder()
Mais je peux maintenant aussi le faire ...
if let firstResponderTextField = UIResponder?.first as? UITextField {
// bla
}
Je voudrais partager avec vous mon implémentation pour trouver le premier répondant dans n’importe où dans UIView. J'espère que cela 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
}
}
Dans Swift 5, il existe l'attribut facultatif "current" dans la classe UIResponder que vous pouvez utiliser pour obtenir le répondeur actuellement actif. Ressemble à ça:
UIResponder.current?.resignFirstResponder()
Mise à jour: je me suis trompé. Vous pouvez en effet utiliser UIApplication.shared.sendAction(_:to:from:for:)
pour appeler le premier intervenant démontré dans ce lien: http://stackoverflow.com/a/14135456/74689 .
La plupart des réponses ne permettent pas de trouver le premier répondant actuel si ce n’est pas dans la hiérarchie des vues. Par exemple, AppDelegate
ou UIViewController
sous-classes.
Il existe un moyen de vous garantir de le trouver même si l'objet du premier répondeur n'est pas un UIView
.
Commençons par implémenter 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'un view
au UIViewController
qui le gère, si le contrôleur de vue est le premier répondant.
Cependant, nous avons toujours besoin d’une résolution descendante, d’un seul var
pour obtenir le premier répondant actuel.
D'abord avec la hiérarchie de vues:
extension UIView {
var previousFirstResponder: UIResponder? {
return nextFirstResponder ?? subviews.compactMap { $0.previousFirstResponder }.first
}
}
Cela cherchera le premier intervenant à l'envers, et s'il ne peut pas le trouver, il dira à 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 à partir de 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 intervenant, vous pouvez appeler:
let firstResponder = UIResponder.first
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;
}