Pergunta

Eu enviei meu aplicativo um pouco mais de uma semana atrás e conseguimos o email rejeição temida hoje. Ele me diz que meu aplicativo não pode ser aceite porque estou usando uma API não pública; Especificamente, ele diz:

A API não-pública que está incluído na sua aplicação é firstResponder.

Agora, a chamada API ofender é realmente uma solução que eu encontrei aqui no SO:

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

Como faço para obter o socorrista atual na tela? Eu estou procurando uma maneira que não será meu aplicativo rejeitado.

Foi útil?

Solução

Em uma das minhas aplicações muitas vezes eu quero que o socorrista a demitir-se as torneiras de usuário no fundo. Para este efeito, eu escrevi uma categoria em UIView, que eu chamo no UIWindow.

O seguinte é baseado em que e deve retornar o socorrista.

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

Exemplo de uso em Swift:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}

Outras dicas

Se o seu objectivo final é apenas a demitir o primeiro respondedor, isso deve funcionar: [self.view endEditing:YES]

Uma maneira comum de manipular o socorrista é usar nil acções específicas. Esta é uma forma de enviar uma mensagem arbitrária para a cadeia de responder (começando com o primeiro respondedor), e continuando abaixo na cadeia até que alguém responde à mensagem (implementou um método correspondente ao selector).

Para o caso de demitir o teclado, esta é a maneira mais eficaz que irá funcionar não importa qual janela ou visão é socorrista:

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

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

(graças a BigZaphod por me lembrar do conceito)

Aqui está uma categoria que permite que você encontre rapidamente o socorrista chamando [UIResponder currentFirstResponder]. Basta adicionar os seguintes dois arquivos 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 o envio de uma ação para nil envia para o socorrista.

(I originalmente publicado esta resposta aqui: https://stackoverflow.com/a/14135456/322427 )

Aqui está uma extensão implementado em Swift com base na resposta mais excelente do 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 que eu renunciar a firstResponder quando eu não sei o que a resposta é:

Criar um UITextField, seja em IB ou programaticamente. Torná-lo escondido. Vinculá-lo até ao seu código se você fez isso em IB.

Então, quando quiser descartar o teclado, você alternar a resposta ao campo de texto invisível, e resignar-lo imediatamente:

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

Para uma versão Swift 3 e 4 de Nevyn é resposta :

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

Aqui está uma solução que relata o socorrista correto (muitas outras soluções não vai relatar um UIViewController como o primeiro respondedor, por exemplo), não necessita de looping sobre a hierarquia vista, e não usa APIs privadas.

Ele aproveita método da Apple sendAction: para: desde: forEvent:. , que já sabe como acessar o socorrista

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

  • Estender UIResponder para que ele possa executar o nosso próprio código no primeiro respondedor.
  • UIEvent subclasse, a fim de devolver o socorrista.

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

A primeira resposta pode ser qualquer instância do UIResponder classe, de forma que há outras classes que podem ser o primeiro a responder, apesar dos UIViews. Por exemplo UIViewController também pode ser o primeiro respondedor.

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

É possível recuperar em seguida, o socorrista fazendo

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

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

No entanto, se o socorrista não é uma subclasse de UIView ou UIViewController, esta abordagem irá falhar.

Para corrigir esse problema, podemos fazer uma abordagem diferente, criando uma categoria em UIResponder e realizar alguma magia swizzeling para ser capaz de construir uma matriz de todas as instâncias que vivem desta classe. Então, para obter a primeira resposta que pudermos simples iterate e peça a cada objeto se -isFirstResponder.

Esta abordagem pode ser encontrada implementado em essa outra essência .

Hope isso ajuda.

Usando Swift e com um objeto UIView específica 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 em sua UIViewController e usá-lo como este:

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

Tome nota que o resultado é um valor Opcional por isso vai ser nulo no caso de não firstResponser foi encontrado nos dados vistas subviews.

iterar sobre os pontos de vista que poderia ser a primeira resposta eo uso - (BOOL)isFirstResponder para determinar se eles estão no momento.

Peter Steinberger apenas twittou sobre o UIWindowFirstResponderDidChangeNotification notificação privada, que você pode observar se você quiser assistir a mudança firstResponder.

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

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

é algo como isto, então, terminou

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

Apenas lo caso aqui é a versão Swift da abordagem de incrível 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
    }

}

Este é o que eu fiz para encontrar o que UITextField é o firstResponder quando o usuário clicar 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];
                }

            }
        }

    }

}

Isto é o que eu tenho em minha categoria UIViewController. Útil para muitas coisas, incluindo a obtenção de 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 em UIResponder, é possível pedir legalmente o objeto UIApplication para dizer-lhe que a primeira resposta é.

Veja este:

existe alguma maneira de pedir uma vista iOS que um de seus filhos tem primeiro estado que responde?

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

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

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 tente, mas parece uma boa solução

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

Eu recomendaria tentar este:

- (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 a recursividade! Não há necessidade de adicionar uma categoria para UIView.

Uso (de 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;
}

Você pode chamar api privite como esta, maçã ignorar:

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

Gostaria de compartilhado com você minha implementação para achado socorrista em qualquer lugar do UIView. Espero que ajude e desculpe pelo meu Inglês. Graças

+ (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 Swift de @thomas -müller a resposta

extension UIView {

    func firstResponder() -> UIView? {
        if self.isFirstResponder() {
            return self
        }

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

        return nil
    }

}

Eu tenho uma abordagem um pouco diferente do que a maioria aqui. Ao invés de percorrer a coleção de vistas olhando para aquele que tem um conjunto isFirstResponder, eu também enviar uma mensagem para nil, mas eu armazenar o receptor (se houver), em seguida, retornar esse valor para que o chamador recebe a instância real (mais uma vez, 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 a primeira resposta, se houver, ao fazer isso ...

return UIResponder.first?.resignFirstResponder()

Mas eu agora também pode fazer isso ...

if let firstResponderTextField = UIResponder?.first as? UITextField {
    // bla
}

código abaixo 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;
}   

Update: eu estava errado. Você pode realmente usar UIApplication.shared.sendAction(_:to:from:for:) para chamar o socorrista demonstrado neste link: http://stackoverflow.com/a/14135456/746890.


A maioria das respostas aqui não pode realmente encontrar o atual primeiro respondedor se não está na hierarquia de vista. Por exemplo, AppDelegate ou UIViewController subclasses.

Há uma maneira de garantir que você encontrá-lo, mesmo se o primeiro objeto que responde não é um UIView.

Primeiro vamos implementar uma versão invertida do mesmo, usando a propriedade next de UIResponder:

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

Com esta propriedade computadorizada, podemos encontrar o primeiro respondedor corrente de baixo para cima, mesmo se não é UIView. Por exemplo, a partir de um view ao UIViewController quem administrá-lo, se o controlador de vista é o primeiro respondedor.

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

Primeiro com a hierarquia de exibição:

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

Isto irá procurar os primeiros trás de resposta, e se ele não poderia encontrá-lo, ele iria dizer seus subviews para fazer a mesma coisa (porque next de seu subexibição não é necessariamente em si). Com isso, podemos encontrá-lo a partir de qualquer ponto de vista, incluindo UIWindow.

E, finalmente, podemos construir o seguinte:

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

Assim, quando você deseja recuperar o socorrista, você pode ligar no:

let firstResponder = UIResponder.first
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top