Obter o primeiro respondedor atual sem usar uma API privada

Eu enviei meu aplicativo há pouco mais de uma semana e recebi hoje o temido e-mail de rejeição. Ele me 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 em seu aplicativo é firstResponder.

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

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

Como obtenho o primeiro socorrista atual na canvas? Estou procurando uma maneira de não rejeitar meu aplicativo.

Em um dos meus aplicativos, muitas vezes quero que o primeiro respondente se demita se o usuário tocar no segundo plano. Para este propósito eu escrevi uma categoria no UIView, que eu 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` } 

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

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

Para o caso de dispensar o teclado, esta é a maneira mais eficaz de trabalhar, independentemente da janela ou da vista:

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

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

(Obrigado ao BigZaphod por me lembrar do conceito)

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

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 

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

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

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

Não é bonito, mas a maneira como renuncio o firstResponder quando não sei qual é o respondente:

Crie um UITextField, no IB ou programaticamente. Torne-o oculto. Vincule-o ao seu código se você fizer isso no IB.

Então, quando você quiser descartar o teclado, você alterna o respondente para o campo de texto invisível e renuncia imediatamente:

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

Aqui está uma solução que relata o primeiro respondedor correto (muitas outras soluções não reportarão um UIViewController como o primeiro respondedor, por exemplo), não requer um loop sobre a hierarquia da visão, e não usa APIs privadas.

Ele utiliza o método sendAtion da Apple : para: from: forEvent:, que já sabe como acessar o primeiro respondedor.

Nós só precisamos ajustá-lo de duas maneiras:

  • Estenda o UIResponder para que ele possa executar nosso próprio código no primeiro respondedor.
  • Subclass UIEvent para 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 

Para uma versão Swift 3 da resposta de nevyn :

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

O primeiro respondedor pode ser qualquer instância da class UIResponder, portanto, há outras classs que podem ser o primeiro respondedor, apesar das UIViews. Por exemplo, o UIViewController também pode ser o primeiro respondedor.

Nesta essência, você encontrará uma maneira recursiva de obter o primeiro respondedor, percorrendo a hierarquia de controladores a partir do rootViewController das janelas do aplicativo.

Você pode recuperar então o primeiro respondedor 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 subclass de UIView ou UIViewController, essa abordagem falhará.

Para corrigir esse problema, podemos fazer uma abordagem diferente criando uma categoria no UIResponder e realizando alguns swizzeling mágicos para poder construir uma matriz de todas as instâncias vivas dessa class. Então, para obter o primeiro respondedor, podemos simplesmente iterar e perguntar a cada object se -isFirstResponder .

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

Espero que ajude.

Usando o Swift e com um object UIView específico, 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 UIViewController e usá-lo assim:

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

Observe que o resultado é um valor Opcional, portanto, será nulo caso nenhum firstResponser seja encontrado nas subvisualizações de visualizações dadas.

Iterar sobre os pontos de vista que poderiam ser o primeiro respondente e use - (BOOL)isFirstResponder para determinar se eles estão no momento.

Peter Steinberger acabou de UIWindowFirstResponderDidChangeNotification sobre a notificação privada UIWindowFirstResponderDidChangeNotification , que você pode observar se quiser observar a alteração do firstResponder.

Se você precisar apenas matar o teclado quando o usuário tocar em uma área de segundo plano, por que não adicionar um reconhecedor de gesto e usá-lo para enviar a mensagem [[self view] endEditing:YES] ?

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

parece algo como isso, em seguida, terminou

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

Isto é o que eu fiz para encontrar o que o 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]; } } } } } 

Apenas o caso aqui é a versão Swift da abordagem do 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 } } 

Isto é o que eu tenho na minha categoria UIViewController. Útil para muitas coisas, inclusive recebendo socorrista. 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; }]; } 

Com uma categoria no UIResponder , é possível solicitar legalmente ao object UIApplication para informar quem é o primeiro respondedor.

Veja isso:

Existe alguma maneira de perguntar a um ponto de vista do iOS qual dos seus filhos tem o status de socorrista?

Você pode tentar também 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

A solução do 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 recomendei 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]); } } } } } } } 

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

Uso (do seu controlador de visualizaçã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; } 

você pode chamar privite api assim, apple ignore:

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

Eu gostaria de compartilhar com você minha implementação para encontrar o primeiro socorrista 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; 

}

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

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

Atualização: eu estava errado. Você pode realmente usar UIApplication.shared.sendAction(_:to:from:for:) para chamar a primeira resposta demonstrada neste link: http://stackoverflow.com/a/14135456/746890 .


A maioria das respostas aqui não pode realmente encontrar o primeiro respondedor atual se ele não estiver na hierarquia de exibição. Por exemplo, subclasss AppDelegate ou UIViewController .

Existe uma maneira de garantir que você o encontre mesmo que o primeiro object respondedor não seja um UIView .

Primeiro, vamos implementar uma versão reversa dele, usando a next propriedade do UIResponder :

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

Com essa propriedade calculada, podemos encontrar o primeiro respondedor atual de baixo para cima, mesmo que não seja o UIView . Por exemplo, de uma view para o UIViewController que está gerenciando, se o controlador de visualização é o primeiro respondedor.

No entanto, ainda precisamos de uma resolução de cima para baixo, um único var para obter o primeiro respondedor atual.

Primeiro com a hierarquia de visualizações:

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

Isso irá procurar o primeiro respondente para trás, e se ele não puder encontrá-lo, ele diria às suas subvisualizações para fazerem a mesma coisa (porque a subvisão next não é necessariamente ela mesma). Com isso podemos encontrá-lo a partir de qualquer visão, incluindo UIWindow .

E finalmente, podemos construir isso:

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

Então, quando você quiser recuperar o primeiro respondedor, você pode chamar:

 let firstResponder = UIResponder.first