Uzyskaj bieżącego pierwszego odpowiadającego bez korzystania z prywatnego interfejsu API

340

Przesłałem aplikację nieco ponad tydzień temu i otrzymałem dziś przerażającą wiadomość e-mail o odrzuceniu. Mówi mi, że mojej aplikacji nie można zaakceptować, ponieważ korzystam z niepublicznego interfejsu API; konkretnie, mówi:

Niepubliczny interfejs API zawarty w aplikacji to firstResponder.

Teraz obrażające wywołanie API jest rozwiązaniem, które znalazłem tutaj na SO:

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

Jak wyświetlić bieżącego pierwszego odpowiadającego na ekranie? Szukam sposobu, który nie spowoduje odrzucenia mojej aplikacji.

Justin Kredible
źródło
5
Zamiast iteracji widoków, możesz użyć tej naprawdę genialnej odrobiny magii: stackoverflow.com/a/14135456/746890
Chris Nolet

Odpowiedzi:

338

W jednej z moich aplikacji często chcę, aby pierwszy odpowiadający zrezygnował, jeśli użytkownik dotknie tła. W tym celu napisałem kategorię na UIView, którą nazywam na UIWindow.

Poniższe informacje są oparte na tym i powinny zwrócić pierwszy obiekt odpowiadający.

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

Szybki:

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

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

        return nil
    }
}

Przykład użycia w Swift:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}
Thomas Müller
źródło
278
Dlaczego jest to lepsze rozwiązanie niż zwykłe dzwonienie [self.view endEditing:YES]?
Tim Sullivan,
69
@ Tym razem nie mogłem tego zrobić. Jest to oczywiście znacznie prostszy sposób zrezygnowania z pierwszej odpowiedzi. Nie pomaga to jednak w przypadku oryginalnych pytań, które polegały na zidentyfikowaniu pierwszego respondenta.
Thomas Müller,
17
Jest to również lepsza odpowiedź, ponieważ możesz chcieć zrobić coś innego z pierwszym respondentem niż zrezygnować ...
Erik B
3
Należy zauważyć, że odnajduje to tylko UIViews, a nie inne UIRespondery, które mogą być również pierwszym odpowiadającym (np. UIViewController lub UIApplication).
Patrick Pijnappel
10
Skąd ta różnica dla iOS 7? czy też nie chciałbyś się w tej sprawie powtórzyć?
Peter DeWeese,
531

Jeśli Twoim ostatecznym celem jest po prostu zrezygnowanie z pierwszego odpowiadającego, powinno to działać: [self.view endEditing:YES]

cdyson37
źródło
122
Czy dla wszystkich, którzy twierdzą, że jest to odpowiedź na „pytanie”, czy możecie spojrzeć na rzeczywiste pytanie? Pytanie dotyczy sposobu uzyskania bieżącego pierwszego odpowiadającego. Nie jak zrezygnować z pierwszego odpowiadającego.
Justin Kredible
2
i gdzie powinniśmy nazwać to self.view endEditing?
user4951,
8
To prawda, że ​​nie jest to dokładnie odpowiedź na pytanie, ale wyraźnie jest to bardzo przydatna odpowiedź. Dzięki!
NovaJoe
6
+1, nawet jeśli nie jest to odpowiedź na pytanie. Dlaczego? Ponieważ wielu przychodzi tutaj z myślą o pytaniu, na które odpowiedź brzmi :) Przynajmniej 267 (w tej chwili) z nich ...
Rok Jarc
1
To nie odpowiada na pytanie.
Sasho,
186

Częstym sposobem manipulowania pierwszym responderem jest użycie zerowych działań ukierunkowanych. Jest to sposób wysłania dowolnej wiadomości do łańcucha odpowiadającego (zaczynając od pierwszego odpowiadającego) i kontynuowania w dół łańcucha, dopóki ktoś nie odpowie na wiadomość (zaimplementował metodę pasującą do selektora).

W przypadku wyłączenia klawiatury jest to najskuteczniejszy sposób, który zadziała bez względu na to, które okno lub widok odpowiada jako pierwszy:

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

To powinno być bardziej skuteczne niż nawet [self.view.window endEditing:YES].

(Podziękowania dla BigZaphod za przypomnienie mi o koncepcji)

Nevyn
źródło
4
To jest takie fajne. Prawdopodobnie najładniejsza sztuczka Cocoa Touch, jaką widziałem na SO. Pomógł mi bez końca!
mluisbrown
jest to zdecydowanie najlepsze rozwiązanie, dzięki milionowi! jest to znacznie lepsze niż powyższe rozwiązanie, ponieważ działa nawet wtedy, gdy aktywne pole tekstowe jest częścią dodatkowego widoku klawiatury (w tym przypadku nie jest to część naszej hierarchii widoków)
Pizzaiola Gorgonzola
2
To rozwiązanie jest najlepszym rozwiązaniem. System już wie, kto jest pierwszym responderem, nie trzeba go znaleźć.
leftspin
2
Chcę dać tę odpowiedź więcej niż jeden głos. To jest cholernie genialne.
Reid Main
1
devios1: Odpowiedź Jakoba jest dokładnie tym, o co pyta pytanie, ale jest to hack na systemie odpowiadającym, zamiast używania systemu odpowiadającego w sposób, w jaki został zaprojektowany. Jest to czystsze i spełnia to, do czego większość ludzi przychodzi na to pytanie, a także w jednym wierszu. (Możesz oczywiście wysłać dowolną wiadomość w stylu akcji docelowej, a nie tylko zrezygnować z funkcji FirstResponder).
Nevyn
98

Oto kategoria, która pozwala szybko znaleźć pierwszego odpowiadającego, dzwoniąc [UIResponder currentFirstResponder]. Wystarczy dodać następujące dwa pliki do swojego projektu:

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

Sztuczka polega na tym, że wysłanie akcji do zera powoduje wysłanie jej do pierwszego obiektu odpowiadającego.

(Oryginalnie opublikowałem tę odpowiedź tutaj: https://stackoverflow.com/a/14135456/322427 )

Jakob Egger
źródło
1
+1 To jest najbardziej eleganckie rozwiązanie problemu (i faktycznie zadane pytanie), jakie widziałem. Wielokrotnego użytku i wydajny! Głosuj na tę odpowiedź!
devios1
3
Brawo. To może być po prostu najpiękniejszy i najbardziej czysty hack, jaki kiedykolwiek widziałem dla Objc ...
Aviel Gross
Bardzo dobrze. Powinien działać naprawdę dobrze, ponieważ był w stanie zapytać pierwszego respondenta, kto (jeśli ktoś) w łańcuchu poradzi sobie z określoną metodą działania, gdyby została wysłana. Możesz użyć tego, aby włączyć lub wyłączyć elementy sterujące w zależności od tego, czy można je obsłużyć. Odpowiem na własne pytanie w tej sprawie i odniosę się do twojej odpowiedzi.
Benjohn
W Swift: stackoverflow.com/questions/1823317/…
Valentin Shergin
Ostrzeżenie: To nie działa, jeśli zaczniesz dodawać wiele okien. Niestety nie mogę jeszcze wskazać przyczyny poza tym.
Heath Borders
41

Oto rozszerzenie zaimplementowane w Swift w oparciu o najdoskonalszą odpowiedź Jakoba Eggera:

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

Szybki 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
źródło
1
Zmodyfikowano odpowiedź na dla swift1.2, gdzie obsługiwane są static var.
nacho4d
Cześć, kiedy wydrukuję wynik tego na nowej aplikacji single-view w viewDidAppear (), dostaję zero. Czy widok nie powinien być pierwszą odpowiedzią?
farhadf
@LususGeffarth Czy nie ma sensu wywoływać metody statycznej currentzamiast first?
Code Commander
Chyba tak, zredagowałem to. Wydaje mi się, że porzuciłem ważną część, skracając nazwę zmiennej.
LinusGeffarth
29

To nie jest ładne, ale sposób, w jaki rezygnuję z pierwszego odpowiedzi, gdy nie wiem, co to jest odpowiadający:

Utwórz pole UITextField w IB lub programowo. Ukryj to. Połącz go ze swoim kodem, jeśli wykonałeś go w IB.

Następnie, gdy chcesz zwolnić klawiaturę, przełączasz odpowiadającego na niewidoczne pole tekstowe i natychmiast go rezygnujesz:

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
Cannyboy
źródło
61
Jest to jednocześnie okropne i genialne. Miły.
Alex Wayne
4
Uważam, że to trochę niebezpieczne - Apple może pewnego dnia zdecydować, że uczynienie ukrytej kontroli pierwszą odpowiedzią nie jest zamierzonym zachowaniem i „naprawić” iOS, aby tego nie robić, a wtedy Twoja sztuczka może przestać działać.
Thomas Tempelmann
2
Lub możesz przypisać firstResponder do UITextField, który jest obecnie widoczny, a następnie wywołać resignFirstResponder na tym konkretnym textField. Eliminuje to potrzebę tworzenia ukrytego widoku. Wszystko to samo, +1.
Swifty McSwifterton
3
Raczej myślę, że to tylko okropne ... może zmienić układ klawiatury (lub widok wprowadzania) przed ukryciem
Daniel
19

W przypadku wersji Swift 3 i 4 odpowiedzi Nevyna :

UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
Pochi
źródło
10

Oto rozwiązanie, które zgłasza poprawnego pierwszego odpowiadającego (wiele innych rozwiązań nie zgłasza na przykład UIViewControllerjako pierwszego odpowiadającego), nie wymaga zapętlania hierarchii widoku i nie korzysta z prywatnych interfejsów API.

Wykorzystuje metodę Apple sendAction: to: from: forEvent:, która już wie, jak uzyskać dostęp do pierwszego respondera.

Musimy go tylko ulepszyć na 2 sposoby:

  • Rozszerz, UIResponderaby mógł wykonać nasz własny kod na pierwszym responderze.
  • Podklasa UIEventw celu zwrócenia pierwszego obiektu odpowiadającego.

Oto kod:

@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
Rozsądny
źródło
7

Pierwszym responderem może być dowolna instancja klasy UIResponder, więc istnieją inne klasy, które mogą być pierwszą odpowiedzią pomimo UIViews. Na przykład UIViewControllermoże być również pierwszy odpowiadający.

W tej liście znajdziesz rekursywny sposób na uzyskanie pierwszego obiektu odpowiadającego, przechodząc przez hierarchię kontrolerów, zaczynając od rootViewController w oknach aplikacji.

Możesz wtedy odzyskać pierwszego odpowiadającego, robiąc

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

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

Jednak jeśli pierwszy obiekt odpowiadający nie jest podklasą UIView lub UIViewController, to podejście zakończy się niepowodzeniem.

Aby rozwiązać ten problem, możemy zastosować inne podejście, tworząc kategorię UIResponderi wykonać magiczne zamiatanie, aby móc zbudować tablicę wszystkich żywych instancji tej klasy. Następnie, aby uzyskać pierwszą odpowiedź, możemy po prostu iterować i pytać każdy obiekt, czy -isFirstResponder.

To podejście można znaleźć zaimplementowane w tej innej istocie .

Mam nadzieję, że to pomoże.

Wilanowowie
źródło
7

Używanie Swift i konkretnego UIViewobiektu może pomóc:

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
}

Po prostu włóż go do swojego UIViewControlleri użyj go w następujący sposób:

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

Zwróć uwagę, że wynik jest wartością opcjonalną, więc będzie zero, na wypadek, gdyby nie znaleziono firstResponser w podviewach podanych widoków.

Jeehut
źródło
5

Powtarzaj poglądy, które mogą być pierwszą odpowiedzią, i użyj ich, - (BOOL)isFirstResponderaby ustalić, czy obecnie są.

Johan Kool
źródło
4

Peter Steinberger właśnie napisał tweeta o prywatnym powiadomieniu UIWindowFirstResponderDidChangeNotification, które możesz obserwować, jeśli chcesz zobaczyć pierwszą zmianę odpowiedzi.

Granice zdrowia
źródło
Został odrzucony, ponieważ korzystanie z prywatnego interfejsu API, być może korzystanie z prywatnego powiadomienia może być złym pomysłem ...
Laszlo
4

Jeśli musisz po prostu zabić klawiaturę, gdy użytkownik stuknie w tło, dlaczego nie dodać rozpoznawania gestów i użyć go do wysłania [[self view] endEditing:YES]wiadomości?

możesz dodać narzędzie do rozpoznawania gestów Tap w pliku xib lub pliku scenorysu i połączyć je z akcją,

wygląda mniej więcej tak, a potem skończył

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}
Arkadi
źródło
To zadziałało dla mnie świetnie. Wybrałem jedno połączenie z widokiem na Xibie i mogę dodać dodatkowe widoki, aby można je było wywoływać w Referencing Outlet Collections
James Perih
4

Wystarczy, że tutaj jest Swift wersja niesamowitego podejścia Jakoba Eggera:

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
źródło
3

Oto, co zrobiłem, aby dowiedzieć się, który obiekt UITextField jest pierwszym odpowiedzią, gdy użytkownik kliknie Zapisz / Anuluj w 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
źródło
2

To właśnie mam w mojej kategorii UIViewController. Przydatny do wielu rzeczy, w tym do uzyskania pierwszej odpowiedzi. Bloki są świetne!

- (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
źródło
2

Możesz wybrać następujące UIViewrozszerzenie, aby je zdobyć (autor: Daniel) :

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }
        return subviews.first(where: {$0.firstResponder != nil })
    }
}
Soheil Novinfard
źródło
1

Możesz spróbować również w ten sposób:

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

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

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

Nie próbowałem tego, ale wydaje się to dobrym rozwiązaniem

Frade
źródło
1

Rozwiązanie z Romea https://stackoverflow.com/a/2799675/661022 jest fajne, ale zauważyłem, że kod wymaga jeszcze jednej pętli. Pracowałem z tableViewController. Zredagowałem skrypt, a potem sprawdziłem. Wszystko działało idealnie.

Poleciłem spróbować tego:

- (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
źródło
Dzięki! Masz rację, większość przedstawionych tutaj odpowiedzi nie jest rozwiązaniem podczas pracy z uitableview. Możesz znacznie uprościć swój kod, nawet wyjąć if if ([textField isKindOfClass:[UITextField class]]), pozwalając na użycie uitextview, i możesz zwrócić pierwszego korespondenta.
Frade
1

To dobry kandydat do rekurencji! Nie musisz dodawać kategorii do UIView.

Użycie (z kontrolera widoku):

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

Kod:

// 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
źródło
1

możesz wywołać prywatny api w ten sposób, Apple ignoruje:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];
ibcker
źródło
1
ale ... skorzystaj z „respondsToSelector”, aby sprawdzić
ibcker
Teraz sprawdzam „ResponsToSelector”, ale wciąż otrzymuję ostrzeżenie, może to być niebezpieczne. Czy mogę jakoś pozbyć się ostrzeżenia?
ecth
1

Swift wersja @ Thomas-Muller odpowiedzi „s

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
źródło
1

Mam nieco inne podejście niż większość tutaj. Zamiast iterować kolekcję widoków szukających tego, który isFirstResponderustawił, ja również wysyłam wiadomość nil, ale przechowuję odbiornik (jeśli istnieje), a następnie zwracam tę wartość, aby osoba dzwoniąca otrzymała rzeczywistą instancję (ponownie, jeśli w ogóle) .

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

Mogę zrezygnować z pierwszego odpowiadającego, jeśli taki istnieje, wykonując to ...

return UIResponder.first?.resignFirstResponder()

Ale teraz mogę to zrobić ...

if let firstResponderTextField = UIResponder?.first as? UITextField {
    // bla
}
Mark A. Donohoe
źródło
1

Chciałbym udostępnić Ci moją implementację, aby znaleźć pierwszą odpowiedź w dowolnym miejscu UIView. Mam nadzieję, że to pomaga i przepraszam za mój angielski. Dzięki

+ (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
źródło
0

Kod poniżej pracy.

- (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
źródło
0

Aktualizacja: Myliłem się. Rzeczywiście możesz użyć, UIApplication.shared.sendAction(_:to:from:for:)aby zadzwonić do pierwszego respondenta pokazanego w tym linku: http://stackoverflow.com/a/14135456/746890 .


Większość odpowiedzi tutaj nie może naprawdę znaleźć bieżącego pierwszego odpowiadającego, jeśli nie znajduje się on w hierarchii widoków. Na przykład AppDelegatelub UIViewControllerpodklasy.

Istnieje sposób, aby zagwarantować, że go znajdziesz, nawet jeśli pierwszym obiektem odpowiadającym nie jest UIView.

Najpierw zaimplementuj jego odwróconą wersję, używając nextwłaściwości UIResponder:

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

Dzięki tej obliczonej właściwości możemy znaleźć bieżącego pierwszego odpowiadającego od dołu do góry, nawet jeśli nie jest UIView. Na przykład od a viewdo tego, UIViewControllerkto nim zarządza, jeśli kontroler widoku jest pierwszym odpowiadającym.

Jednak nadal potrzebujemy odgórnej rozdzielczości, pojedynczej, varaby uzyskać obecną pierwszą odpowiedź.

Najpierw z hierarchią widoków:

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

Spowoduje to wyszukanie pierwszego odpowiadającego wstecz i gdyby nie mógł go znaleźć, powiedziałby swoim podwykonawcom, aby zrobili to samo (ponieważ jego widok podrzędny nextniekoniecznie jest sam). Dzięki temu możemy go znaleźć z dowolnego widoku, w tym UIWindow.

I wreszcie możemy zbudować to:

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

Więc jeśli chcesz odzyskać pierwszego respondenta, możesz zadzwonić:

let firstResponder = UIResponder.first
tak
źródło
0

Najprostszy sposób na znalezienie pierwszego odpowiadającego:

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

Domyślna implementacja wywołuje metodę akcji do podanego obiektu docelowego lub, jeśli nie określono żadnego celu, do pierwszego obiektu odpowiadającego.

Następny krok:

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

Zastosowanie: Po prostu weź UIResponder.actualFirstwłasne cele.

Alexey
źródło