Obtenha a primeira resposta atual sem usar uma API privada

340

Enviei meu aplicativo há pouco mais de uma semana e recebi o temido e-mail de rejeição hoje. Ele diz que meu aplicativo não pode ser aceito porque estou usando uma API não pública; especificamente, diz,

A API não pública incluída no seu aplicativo é firstResponder.

Agora, a chamada da API ofensiva é na verdade uma solução que encontrei aqui no SO:

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

Como obtenho a primeira resposta atual na tela? Estou procurando uma maneira de não rejeitar meu aplicativo.

Justin Kredible
fonte
5
Em vez de iteração sobre pontos de vista, você pode usar este verdadeiramente brilhante pouco de magia: stackoverflow.com/a/14135456/746890
Chris Nolet

Respostas:

338

Em um dos meus aplicativos, muitas vezes quero que o primeiro atendente se demita se o usuário tocar em segundo plano. Para esse fim, escrevi uma categoria no UIView, que chamo no UIWindow.

O seguinte é baseado nisso e deve retornar o primeiro respondedor.

@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 ou superior

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

Rápido:

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }

        for subview in subviews {
            if let firstResponder = subview.firstResponder {
                return firstResponder
            }
        }

        return nil
    }
}

Exemplo de uso no Swift:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}
Thomas Müller
fonte
278
Por que essa é uma solução melhor do que simplesmente ligar [self.view endEditing:YES]?
Tim Sullivan
69
@ Tim eu não, você poderia fazer isso. É obviamente uma maneira muito mais simples de se demitir do primeiro socorrista. Porém, não ajuda com as perguntas originais, que foram identificar o primeiro respondedor.
Thomas Müller
17
Além disso, esta é uma resposta melhor, porque você pode querer fazer outra coisa com o socorrista do que demitindo-lo ...
Erik B
3
Deve-se observar que isso só encontra UIViews, não outros UIResponders que também poderiam ser o primeiro a responder (por exemplo, UIViewController ou UIApplication).
Patrick Pijnappel
10
Por que a diferença para o iOS 7? você também não gostaria de se retribuir nesse caso?
31414 Peter PWeese
531

Se seu objetivo final é apenas renunciar ao primeiro respondedor, isso deve funcionar: [self.view endEditing:YES]

cdyson37
fonte
122
Para todos vocês dizendo que esta é a resposta para a "pergunta", você pode dar uma olhada na pergunta real? A pergunta pergunta como obter o primeiro socorrista atual. Não é como renunciar ao primeiro respondedor.
perfil completo de Justin Kredible
2
e onde devemos chamar isso self.view endEditing?
user4951
8
É verdade que isso não está respondendo exatamente à pergunta, mas claramente é uma resposta muito útil. Obrigado!
NovaJoe
6
+1 mesmo que não seja uma resposta para a pergunta. Por quê? Porque muitos vêm aqui com uma pergunta em mente que esta resposta respostas :) Pelo menos 267 (no momento) deles ...
Rok JARC
11
Isso não está respondendo à pergunta.
Sasho
186

Uma maneira comum de manipular o primeiro respondedor é usar ações direcionadas nulas. Essa é uma maneira de enviar uma mensagem arbitrária para a cadeia de respostas (começando com a primeira) e continuar na cadeia até que alguém responda à mensagem (implementou um método correspondente ao seletor).

No caso de descartar o teclado, esta é a maneira mais eficaz de funcionar, independentemente da janela ou exibição que responder primeiro:

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

Isso deve ser mais eficaz do que par [self.view.window endEditing:YES].

(Obrigado ao BigZaphod por me lembrar do conceito)

Nevyn
fonte
4
Isso é tão legal. Possivelmente, o melhor truque do Cocoa Touch que eu já vi no SO. Me ajudou sem fim!
mluisbrown
esta é DE longe a melhor solução, graças a um milhão! este é muito melhor do que a solução acima este, porque ele funciona mesmo do campo de texto ativo é parte da visão acessório do teclado (nesse caso, não faz parte de nossa hierarquia de vista)
Pizzaiola Gorgonzola
2
Esta solução é a melhor prática. O sistema já sabe quem é o primeiro responsável pela resposta, não há necessidade de encontrá-lo.
precisa saber é o seguinte
2
Quero dar a essa resposta mais de uma votação positiva. Este é um talento genial.
Reid Principal
11
devios1: A resposta de Jakob é exatamente o que a pergunta pede, mas é uma invasão no topo do sistema de resposta, em vez de usar o sistema de resposta da maneira como foi projetado. Isso é mais limpo e faz com que a maioria das pessoas chegue a essa questão, e de uma só vez. (É claro que você pode enviar qualquer mensagem no estilo de ação de destino, e não apenas resignFirstResponder).
Nevyn
98

Aqui está uma categoria que permite encontrar rapidamente o primeiro respondedor ligando [UIResponder currentFirstResponder]. Basta adicionar os dois arquivos a seguir ao seu projeto:

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

O truque aqui é que enviar uma ação para zero a envia para o primeiro respondedor.

(Eu originalmente publiquei esta resposta aqui: https://stackoverflow.com/a/14135456/322427 )

Jakob Egger
fonte
11
+1 Esta é a solução mais elegante para o problema (e a pergunta real) que eu já vi. Reutilizável e eficiente! Voto positivo nesta resposta!
devios1
3
Bravo. Isso pode ser apenas a mais bela e limpa corte que eu já vi para ObjC ...
Aviel Gross
Muito agradável. Deve funcionar muito bem para poder perguntar ao primeiro respondente quem (se houver) na cadeia manipulará um método de ação específico, caso seja enviado. Você pode usar isso para ativar ou desativar os controles com base em se eles podem ser manipulados. Vou responder minha própria pergunta sobre isso e referenciar sua resposta.
Benjohn
Em Swift: stackoverflow.com/questions/1823317/…
Valentin Shergin
Aviso: Isso não funciona se você começar a adicionar várias janelas. Infelizmente, ainda não posso apontar uma causa além disso.
Heath Borders
41

Aqui está uma extensão implementada no Swift com base na resposta mais excelente 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
    }
}
Code Commander
fonte
11
Resposta modificada para swift1.2, em que a var estática é suportada.
N
Oi, quando imprimo o resultado disso em um novo aplicativo de exibição única em viewDidAppear (), fico sem nada. A visualização não deveria ser a primeira a responder?
farhadf
@LinusGeffarth Não faz mais sentido chamar o método estático em currentvez defirst ?
Code Commander
Acho que sim, editei. Parece que perdi a parte importante ao abreviar o nome da variável.
7608 LinearGeffarth
29

Não é bonito, mas a maneira como eu renuncio o firstResponder quando não sei o que é esse respondedor:

Crie um UITextField, no IB ou programaticamente. Torná-lo oculto. Vincule-o ao seu código se você o criou no IB.

Então, quando você deseja descartar o teclado, alterne o respondedor para o campo de texto invisível e o renuncie imediatamente:

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
cannyboy
fonte
61
Isso é horrível e genial ao mesmo tempo. Agradável.
Alex Wayne
4
Acho um pouco perigoso - a Apple pode decidir um dia que fazer um controle oculto se tornar o primeiro respondedor não é o comportamento pretendido e "consertar" o iOS para não fazer mais isso, e seu truque pode parar de funcionar.
Thomas Tempelmann
2
Ou você pode atribuir o firstResponder a um UITextField que está visível no momento e chamar resignFirstResponder nesse textField específico. Isso elimina a necessidade de criar uma exibição oculta. Mesmo assim, +1.
Swifty McSwifterton
3
Eu prefiro achar horrível ... pode mudar o layout do teclado (ou a visualização de entrada) antes de ocultá-lo
Daniel
19

Para uma versão Swift 3 e 4 do da resposta nevyn :

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

Aqui está uma solução que reporta o socorrista correto (muitas outras soluções não relatam uma UIViewController como o primeiro respondedor, por exemplo), não requer loop na hierarquia de exibição e não usa APIs privadas.

Ele aproveita o método da Apple sendAction: to: from: forEvent:, que já sabe como acessar o primeiro respondedor.

Só precisamos ajustá-lo de duas maneiras:

  • Ampliar UIResponder para que ele possa executar nosso próprio código na primeira resposta.
  • Subclasse UIEventpara retornar o primeiro respondedor.

Aqui está o código:

@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ível
fonte
7

O primeiro respondedor pode ser qualquer instância da classe UIResponder; portanto, há outras classes que podem ser o primeiro a responder, apesar dos UIViews. Por exemploUIViewController também pode ser o primeiro a responder.

Em esta essência você vai encontrar uma maneira recursiva para obter a primeira resposta por looping através da hierarquia de controladores a partir do RootViewController das janelas do aplicativo.

Você pode recuperar o primeiro atendedor, fazendo

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

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

No entanto, se o primeiro respondedor não for uma subclasse de UIView ou UIViewController, essa abordagem falhará.

Para corrigir esse problema, podemos fazer uma abordagem diferente, criando uma categoria UIRespondere executando alguns swizzeling mágicos para poder construir uma matriz de todas as instâncias vivas dessa classe. Em seguida, para obter o primeiro respondedor, podemos iterar e perguntar a cada objeto se -isFirstResponder.

Esta abordagem pode ser encontrada implementada nesta outra essência .

Espero que ajude.

vilanovi
fonte
7

Usando o Swift e com um objeto específico,UIView isso pode ajudar:

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
}

Basta colocá-lo no seu UIViewControllere usá-lo assim:

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

Observe que o resultado é um valor opcional, portanto será nulo caso nenhum firstResponser tenha sido encontrado nas subvisões de visualizações fornecidas.

Jeehut
fonte
5

Itere sobre as visualizações que poderiam ser o primeiro respondedor e use - (BOOL)isFirstResponderpara determinar se elas estão atualmente.

Johan Kool
fonte
4

Peter Steinberger acabou de twittar sobre a notificação privada UIWindowFirstResponderDidChangeNotification, que você pode observar se quiser assistir à primeira alteração do Responsável.

Heath Borders
fonte
Ele ter rejeitado porque usar uma API privada, talvez usando uma notificação privada também poderia ser uma má idéia ...
Laszlo
4

Se você só precisa matar o teclado quando o usuário toca em uma área de fundo, por que não adicionar um reconhecedor de gestos e usá-lo para enviar o [[self view] endEditing:YES] mensagem?

você pode adicionar o reconhecedor de gestos ao toque no arquivo xib ou storyboard e conectá-lo a uma ação,

parece algo assim depois terminou

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}
Arkadi
fonte
Isso funcionou muito bem para mim. Eu escolhi uma conexão com uma vista sobre o Xib, e eu posso adicionar visualizações adicionais para este invocar em referenciamento Tomada Colecções
James Perih
4

Apenas neste caso, aqui está a versão Swift da incrível abordagem 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
fonte
3

Foi isso que fiz para descobrir qual UITextField é o firstResponder quando o usuário clica em Salvar / Cancelar em um 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];
                }

            }
        }

    }

}
Romeo
fonte
2

É isso que tenho na minha categoria UIViewController. Útil para muitas coisas, incluindo a primeira resposta. Blocos são ótimos!

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

Você pode escolher a seguinte UIViewextensão para obtê-lo (crédito por Daniel) :

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

Você também pode tentar assim:

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

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

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

Eu não tentei, mas parece uma boa solução

Frade
fonte
1

A solução da romeo https://stackoverflow.com/a/2799675/661022 é legal, mas notei que o código precisa de mais um loop. Eu estava trabalhando com tableViewController. Eu editei o script e depois verifiquei. Tudo funcionou perfeitamente.

Eu recomendo tentar isso:

- (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
fonte
Obrigado! Você está correto, a maioria das respostas apresentadas aqui não são uma solução ao trabalhar com o uitableview. Você pode simplificar muito seu código, até mesmo remover o if if ([textField isKindOfClass:[UITextField class]]), permitindo o uso do uitextview, e pode retornar o responsável pela resposta.
Frade
1

Este é um bom candidato para recursão! Não há necessidade de adicionar uma categoria ao UIView.

Uso (do seu controlador de exibição):

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

Código:

// 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
fonte
1

você pode chamar API privada assim, a apple ignora:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];
ibcker
fonte
11
mas ... por favor, use 'respondesToSelector' para verificar
ibcker 11/11
Verifico 'responsToSelector' agora, mas ainda recebo um aviso, isso pode não ser seguro. Posso me livrar do aviso de alguma forma?
ecth
1

Versão rápida da resposta 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
fonte
1

Eu tenho uma abordagem um pouco diferente da maioria aqui. Em vez de percorrer a coleção de visualizações procurando a que foi isFirstResponderconfigurada, eu também envio uma mensagem para nil, mas armazeno o destinatário (se houver) e, em seguida, retorno esse valor para que o chamador obtenha a instância real (novamente, se houver) .

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

Posso então renunciar ao primeiro atendedor, se houver, fazendo isso ...

return UIResponder.first?.resignFirstResponder()

Mas agora também posso fazer isso ...

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

Gostaria de compartilhar com você minha implementação para encontrar o primeiro respondedor em qualquer lugar do UIView. Espero que ajude e desculpe pelo meu inglês. obrigado

+ (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
fonte
0

Código abaixo do trabalho.

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

Atualização: eu estava errado. Você pode usar UIApplication.shared.sendAction(_:to:from:for:)o primeiro respondedor demonstrado neste link: http://stackoverflow.com/a/14135456/746890 .


A maioria das respostas aqui não consegue encontrar o primeiro respondente atual, se ele não estiver na hierarquia de visualizações. Por exemplo, AppDelegateou UIViewControllersubclasses.

Existe uma maneira de garantir que você o encontre, mesmo que o primeiro objeto de resposta não seja a UIView.

Primeiro vamos implementar uma versão invertida, usando a nextpropriedade de UIResponder:

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

Com essa propriedade computada, podemos encontrar o primeiro atendedor atual de baixo para cima, mesmo que não seja UIView. Por exemplo, de um viewpara oUIViewController quem está gerenciando, se o controlador de exibição for o primeiro a responder.

No entanto, ainda precisamos de uma resolução de cima para baixo, uma única varpara obter o primeiro atendedor atual.

Primeiro com a hierarquia de visualizações:

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

Isso procurará o primeiro respondente de trás para frente e, se não o encontrasse, instruiria suas sub-visualizações a fazer a mesma coisa (porque as sub-visualizações nextnão são necessariamente elas mesmas ). Com isso, podemos encontrá-lo de qualquer ponto de vista, inclusive UIWindow.

E, finalmente, podemos construir isso:

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

Portanto, quando você deseja recuperar a primeira resposta, pode ligar para:

let firstResponder = UIResponder.first
yesleon
fonte
0

Maneira mais simples de encontrar o socorrista:

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

A implementação padrão despacha o método de ação para o objeto de destino especificado ou, se nenhum destino for especificado, para o primeiro respondedor.

Próxima Etapa:

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

Uso: Basta obter UIResponder.actualFirstpara seus próprios propósitos.

Alexey
fonte