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

340

J'ai soumis mon application il y a un peu plus d'une semaine et j'ai reçu l'e-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; en particulier, il est 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 afficher le premier répondeur actuel à l'écran? Je cherche un moyen de ne pas rejeter mon application.

Justin Kredible
la source
5
Plutôt que d'itérer sur les vues, vous pouvez utiliser ce morceau de magie vraiment brillant : stackoverflow.com/a/14135456/746890
Chris Nolet

Réponses:

338

Dans l'une de mes applications, je souhaite souvent que le premier répondant démissionne si l'utilisateur appuie sur l'arrière-plan. À cet effet, 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;
}

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`
}
Thomas Müller
la source
278
Pourquoi est-ce une meilleure solution que de simplement appeler [self.view endEditing:YES]?
Tim Sullivan
69
@Tim Je ne savais pas que tu pouvais faire ça. C'est évidemment un moyen beaucoup plus simple de démissionner du premier répondant. Cela n'aide pas pour les questions originales, qui était d'identifier le premier répondant.
Thomas Müller
17
De plus, c'est une meilleure réponse car vous voudrez peut-être faire autre chose avec le premier répondant que de le démissionner ...
Erik B
3
Il convient de noter que cela ne trouve que les UIViews, pas les autres répondeurs UIR qui pourraient également être le premier répondant (par exemple UIViewController ou UIApplication).
Patrick Pijnappel
10
Pourquoi la différence pour iOS 7? ne voudriez-vous pas également demander une récession dans ce cas?
Peter DeWeese
531

Si votre objectif ultime est simplement de démissionner du premier répondant, cela devrait fonctionner: [self.view endEditing:YES]

cdyson37
la source
122
Pour tous ceux qui disent que c'est la réponse à la "question", pouvez-vous jeter un coup d'œil à la vraie question? La question demande comment obtenir le premier répondant actuel. Pas comment démissionner le premier répondant.
Justin Kredible
2
et où devrions-nous appeler ce self.view endEditing?
user4951
8
Il est vrai que cela ne répond pas exactement à la question, mais c'est clairement une réponse très utile. Merci!
NovaJoe
6
+1 même si ce n'est pas une réponse à la question. Pourquoi? Parce que beaucoup viennent ici avec une question à l'esprit à laquelle cette réponse répond :) Au moins 267 (pour le moment) d'entre eux ...
Rok Jarc
1
Cela ne répond pas à la question.
Sasho
186

Une manière courante de manipuler le premier intervenant consiste à utiliser zéro actions ciblées. Il s'agit d'un moyen d'envoyer un message arbitraire à la chaîne de répondeurs (en commençant par le premier répondant) et de continuer le long de la chaîne 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 fermeture du clavier, c'est le moyen le plus efficace qui fonctionnera 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)

Nevyn
la source
4
C'est trop cool. Peut-être l'astuce Cocoa Touch la plus soignée que j'ai jamais vue sur SO. M'a aidé sans fin!
mluisbrown
c'est de loin la meilleure solution, merci mille fois! c'est bien mieux que la solution ci-dessus, car cela fonctionne même si le champ de texte actif fait partie de la vue accessoire du clavier (dans ce cas, il ne fait pas partie de notre hiérarchie de vues)
Pizzaiola Gorgonzola
2
Cette solution est la meilleure solution. Le système sait déjà qui est le premier répondant, il n'est pas nécessaire de le trouver.
leftspin
2
Je veux donner à cette réponse plus d'un vote positif. C'est génial.
Reid Main
1
devios1: La réponse de Jakob est exactement ce que la question demande, mais c'est un hack au-dessus du système de répondeur, plutôt que d'utiliser le système de répondeur comme il a été conçu. C'est plus propre et accomplit ce pour quoi la plupart des gens viennent à cette question, et dans un seul revêtement aussi. (Vous pouvez bien sûr envoyer n'importe quel message de style d'action cible, et pas seulement resignFirstResponder).
Nevyn
98

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

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 )

Jakob Egger
la source
1
+1 C'est la solution la plus élégante au problème (et la question posée) que j'ai encore vue. Réutilisable et efficace! Votez pour cette réponse!
devios1
3
Bravo. Cela pourrait être le hack le plus beau et le plus propre que j'aie jamais vu pour Objc ...
Aviel Gross
Très agréable. Cela devrait très bien fonctionner pour pouvoir demander au premier répondant qui (le cas échéant) dans la chaîne gérera une méthode d'action particulière si elle devait être envoyée. Vous pouvez l'utiliser pour activer ou désactiver les contrôles selon qu'ils peuvent être gérés. Je vais répondre à ma propre question à ce sujet et référencer votre réponse.
Benjohn
Dans Swift: stackoverflow.com/questions/1823317/…
Valentin Shergin
Avertissement: cela ne fonctionne pas si vous commencez à ajouter plusieurs fenêtres. Je ne peux malheureusement pas encore indiquer une cause au-delà de cela.
Heath Borders
41

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
    }
}
Commandant de code
la source
1
Réponse modifiée à pour swift1.2 où la var statique est prise en charge.
nacho4d
Salut, lorsque j'imprime le résultat de cela sur une toute nouvelle application à vue unique dans viewDidAppear (), je reçois zéro. La vue ne devrait-elle pas être le premier intervenant?
farhadf
@LinusGeffarth N'est-il pas plus logique d'appeler la méthode statique à la currentplace de first?
Code Commander
Devinez donc, édité. Il me semble avoir abandonné la partie importante lors de l'abréviation du nom de la variable.
LinusGeffarth
29

Ce n'est pas joli, mais la façon dont je démissionne du premier répondeur quand je ne sais pas ce qu'est le répondeur:

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

Ensuite, lorsque vous souhaitez fermer le clavier, vous basculez le répondeur vers le champ de texte invisible et vous le résignez immédiatement:

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
cannyboy
la source
61
C'est à la fois horrible et génial à la fois. Agréable.
Alex Wayne
4
Je trouve cela un peu dangereux - Apple pourrait décider un jour que faire un contrôle caché devenir le premier intervenant n'est pas un comportement prévu et "corriger" iOS pour qu'il ne fasse plus cela, et alors votre astuce pourrait cesser de fonctionner.
Thomas Tempelmann
2
Ou vous pouvez affecter le premier Répondeur à un UITextField qui est actuellement visible, puis appeler resignFirstResponder sur ce textField particulier. Cela supprime la nécessité de créer une vue masquée. Tout de même, +1.
Swifty McSwifterton
3
Je pense plutôt que c'est seulement horrible ... cela peut changer la disposition du clavier (ou la vue d'entrée) avant de le cacher
Daniel
19

Pour une version Swift 3 et 4 de la réponse de Nevyn :

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

Voici une solution qui signale le premier répondant correct (de nombreuses autres solutions ne signalent pas un UIViewControllercomme premier répondant, par exemple), ne nécessite pas de bouclage sur la hiérarchie des vues et n'utilise pas d'API privées.

Il exploite la méthode sendAction: d' Apple vers: from: forEvent:, qui sait déjà comment accéder au premier répondant.

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

  • Étendez-le UIResponderafin qu'il puisse exécuter notre propre code sur le premier répondant.
  • Sous-classe UIEventafin 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
Sensé
la source
7

Le premier répondant peut être n'importe quelle instance de la classe UIResponder, il existe donc d'autres classes qui pourraient être le premier répondant malgré les UIViews. Par exemple, UIViewControllerpourrait également être le premier répondant.

Dans cet aperçu, vous trouverez un moyen récursif d'obtenir le premier répondeur 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épondeur 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 UIResponderet en effectuant un tour de magie pour 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 implémentée dans cet autre sens .

J'espère que ça aide.

vilanovi
la source
7

En utilisant Swift et avec un objet spécifique UIView, 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 UIViewControlleret utilisez-le comme ceci:

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

Notez que le résultat est une valeur facultative , il sera donc nul si aucun firstResponser n'a été trouvé dans les sous-vues vues données.

Jeehut
la source
5

Répétez les vues qui pourraient être le premier répondant et utilisez - (BOOL)isFirstResponderpour déterminer si elles le sont actuellement.

Johan Kool
la source
4

Peter Steinberger vient de tweeter à propos de la notification privée UIWindowFirstResponderDidChangeNotification, que vous pouvez observer si vous souhaitez regarder le premier changement de répondeur.

Frontières de la santé
la source
Il est rejeté parce que l'utilisation d'une API privée, peut-être que l'utilisation d'une notification privée pourrait également être une mauvaise idée ...
Laszlo
4

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 geste et l'utiliser pour envoyer le [[self view] endEditing:YES]message?

vous pouvez ajouter le reconnaisseur de gestes Tap dans le fichier xib ou storyboard et le connecter à une action,

ressemble à quelque chose comme ça, puis terminé

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}
Arkadi
la source
Cela a très bien fonctionné pour moi. J'ai choisi une connexion à une vue sur le Xib, et je peux ajouter des vues supplémentaires à invoquer dans Referencing Outlet Collections
James Perih
4

Juste le cas ici est la version Swift de l'approche impressionnante 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
    }

}
Valentin Shergin
la source
3

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

            }
        }

    }

}
Roméo
la source
2

C'est ce que j'ai dans ma catégorie UIViewController. Utile pour de nombreuses choses, notamment 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;
 }];
}
Andrei Tchijov
la source
2

Vous pouvez choisir l' UIViewextension suivante pour l'obtenir (crédit de Daniel) :

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }
        return subviews.first(where: {$0.firstResponder != nil })
    }
}
Soheil Novinfard
la source
1

Vous pouvez également 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 semble une bonne solution

Frade
la source
1

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 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]);
                        }
                    }
                }
            }
        }
    }
}
pedrouan
la source
Merci! Vous avez raison, la plupart des réponses présentées ici ne sont pas une solution lorsque vous travaillez avec uitableview. Vous pourriez beaucoup simplifier votre code, même retirer le if if ([textField isKindOfClass:[UITextField class]]), permettant l'utilisation d'uitextview, et retourner le premier répondant.
Frade
1

C'est un bon candidat pour la récursivité! Pas besoin d'ajouter 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;
}
Pétur Ingi Egilsson
la source
1

vous pouvez appeler une API privée comme celle-ci, Apple ignore:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];
ibcker
la source
1
mais ... veuillez utiliser '
respondsToSelector
Je coche maintenant 'respondsToSelector' mais je reçois toujours un avertissement, cela pourrait être dangereux. Puis-je me débarrasser de l'avertissement d'une manière ou d'une autre?
2015
1

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
    }

}
Kádi
la source
1

J'ai une approche légèrement différente de la plupart ici. Plutôt que de parcourir la collection de vues à la recherche de celle qui a été isFirstResponderdéfinie, j'envoie également un message à nil, mais je stocke le récepteur (le cas échéant), puis je renvoie cette valeur afin 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 ensuite démissionner du premier intervenant, le cas échéant, en procédant ainsi ...

return UIResponder.first?.resignFirstResponder()

Mais je peux maintenant aussi faire ça ...

if let firstResponderTextField = UIResponder?.first as? UITextField {
    // bla
}
Mark A. Donohoe
la source
1

Je voudrais partager avec vous mon implémentation pour trouver le premier répondant partout 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;
}
Rubens Iotti
la source
0

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;
}   
John Chen
la source
0

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 peuvent pas vraiment trouver le premier répondant actuel s'il n'est pas dans la hiérarchie des vues. Par exemple, AppDelegateou des UIViewControllersous - classes.

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

Permet d'abord d'en implémenter une version inversée, en utilisant la nextpropriété 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 s'il ne l'est pas UIView. Par exemple, de a viewà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, une seule varpour obtenir le premier répondeur 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 à l'envers, et s'il ne le trouve pas, il dira à ses sous-vues de faire la même chose (parce que ses sous-vues ne nextsont pas nécessairement elles-mêmes). Avec cela, nous pouvons le trouver de n'importe quel point de 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
yesleon
la source
0

Le moyen le plus simple de trouver le premier répondant:

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

L'implémentation par défaut envoie la méthode d'action à l'objet cible donné ou, si aucune cible n'est spécifiée, au premier répondeur.

L'étape suivante:

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

Utilisation: obtenez simplement UIResponder.actualFirstpour vos propres besoins.

Alexey
la source