Question

J'ai envoyé 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; en particulier, il est dit,

  

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

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 est-ce que le premier répondant actuel est affiché à l'écran? Je cherche un moyen de ne pas rejeter mon application.

Était-ce utile?

La solution

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’ai appelée 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`
}

Autres conseils

Si votre but 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 intervenant consiste à n’utiliser aucune action ciblée. C’est une façon d’envoyer un message arbitraire à la chaîne de répondeurs (en commençant par le premier répondant) et de poursuivre en aval jusqu'à ce que quelqu'un réponde au message (a mis en œuvre une méthode correspondant au sélecteur).

Dans 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 m'avoir rappelé 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 <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

Le truc, c’est que l’envoi d’une action à nil l’envoie au premier intervenant.

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

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 beau, mais je résigne le premier correspondant quand je ne sais pas ce que le répondant est:

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 masquer 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 un Swift 3 & amp; 4 version de La réponse de Nevyn :

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 en tant que premier répondant, par exemple), ne nécessitent pas de boucle sur la hiérarchie de la vue et ne le font pas. ne pas utiliser les 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.

Il suffit de le modifier de deux manières:

  • Étendez UIResponder afin 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

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 peut également être le premier intervenant.

Dans ceci en substance , vous trouverez un moyen récursif d'obtenir le premier répondant en effectuant une boucle entre les éléments hiérarchie des contrôleurs à partir du rootViewController des fenêtres de l'application.

Vous pouvez récupérer ensuite 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épondant 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 en effectuant quelques opérations magiques afin de pouvoir créer un tableau de toutes les instances vivantes de cette classe. Ensuite, pour obtenir le premier répondant, nous pouvons simplement itérer et demander à chaque objet si -isFirstResponder .

Cette approche peut être trouvée dans cette autre caractéristique .

J'espère que ça aide.

L'utilisation de Swift et d'un objet spécifique UIView peut 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 suit:

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

Notez que le résultat est une valeur facultative . Par conséquent, il sera nul si aucune firstResponser n'a été trouvée dans les sous-vues des vues fournies.

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

Peter Steinberger a juste tweeté à propos de la notification privée UIWindowFirstResponderDidChangeNotification , qui vous pouvez observer si vous voulez regarder le premier changement de répondeur.

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 à quelque chose comme ça puis fini

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

Juste au cas où voici la version Swift 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 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 géniaux!

- (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.

Voir ceci:

Existe-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 UIView suivante pour l'obtenir (crédit de Daniel) :

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }
        return subviews.first(where: {<*>.firstResponder != nil })
    }
}

Vous pouvez aussi essayer comme ceci:

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

La solution de romeo https://stackoverflow.com/a/2799675/661022 est cool, mais j'ai remarqué que le code a besoin d'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]);
                        }
                    }
                }
            }
        }
    }
}

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

vous pouvez appeler une API privée comme ceci, Apple ignorer:

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

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 @thomas La réponse de -m & # 252; 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
    }

}

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 défini isFirstResponder , j'envoie également un message à nil , mais je stocke le récepteur (le cas échéant), puis renvoyer cette valeur pour que l'appelant obtienne l'instance réelle (à nouveau, 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
}

Le code ci-dessous fonctionne.

- (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: je me suis trompé. 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 ne permettent pas de trouver le premier répondant 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 objet de réponse n'est pas un UIView .

D'abord, implémentons 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, même s'il ne s'agit pas de UIView . Par exemple, d'une vue 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 { 
extension UIResponder {
    static var first: UIResponder? {
        return UIApplication.shared.windows.compactMap({ 
let firstResponder = UIResponder.first
.previousFirstResponder }).first } }
.previousFirstResponder }.first } }

Ceci recherchera le premier répondant à l'envers, et s'il ne le trouve pas, il dira à ses sous-vues de faire la même chose (parce que le suivant de sa sous-vue n'est pas nécessairement lui-même). Avec cela, nous pouvons le trouver à partir de n’importe quelle vue, y compris UIWindow .

Et enfin, nous pouvons construire ceci:

<*>

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

<*>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top