Holen Sie sich den aktuellen Ersthelfer ohne Verwendung einer privaten API


339

Ich habe meine App vor etwas mehr als einer Woche eingereicht und heute die gefürchtete Ablehnungs-E-Mail erhalten. Es sagt mir, dass meine App nicht akzeptiert werden kann, weil ich eine nicht öffentliche API verwende. konkret heißt es:

Die nicht öffentliche API, die in Ihrer Anwendung enthalten ist, ist firstResponder.

Nun ist der beleidigende API-Aufruf tatsächlich eine Lösung, die ich hier auf SO gefunden habe:

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

Wie bekomme ich den aktuellen Ersthelfer auf den Bildschirm? Ich suche nach einer Möglichkeit, meine App nicht abzulehnen.


5
Anstatt über Ansichten zu iterieren, können Sie dieses wirklich brillante Stück Magie verwenden: stackoverflow.com/a/14135456/746890
Chris Nolet

Antworten:


338

In einer meiner Anwendungen möchte ich oft, dass der Ersthelfer zurücktritt, wenn der Benutzer auf den Hintergrund tippt. Zu diesem Zweck habe ich eine Kategorie in UIView geschrieben, die ich im UIWindow aufrufe.

Das Folgende basiert darauf und sollte den Ersthelfer zurückgeben.

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

Schnell:

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

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

        return nil
    }
}

Anwendungsbeispiel in Swift:

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

278
Warum ist dies eine bessere Lösung als nur anzurufen [self.view endEditing:YES]?
Tim Sullivan

69
@ Tim, ich habe nicht, dass du das machen könntest. Es ist offensichtlich eine viel einfachere Möglichkeit, den Ersthelfer zurückzutreten. Es hilft jedoch nicht bei den ursprünglichen Fragen, den Ersthelfer zu identifizieren.
Thomas Müller

17
Dies ist auch eine bessere Antwort, da Sie möglicherweise etwas anderes mit dem Ersthelfer tun möchten, als es zurückzutreten ...
Erik B

3
Es ist zu beachten, dass hier nur UIViews gefunden werden, nicht andere UIResponder, die auch der Ersthelfer sein könnten (z. B. UIViewController oder UIApplication).
Patrick Pijnappel

10
Warum der Unterschied für iOS 7? Würdest du in diesem Fall nicht auch zurückgreifen wollen?
Peter DeWeese

531

Wenn Ihr letztendliches Ziel nur darin besteht, den Ersthelfer zurückzutreten, sollte dies funktionieren: [self.view endEditing:YES]


122
Können Sie einen Blick auf die eigentliche Frage werfen, wenn Sie alle sagen, dass dies die Antwort auf die "Frage" ist? Die Frage fragt, wie man den aktuellen Ersthelfer bekommt. Nicht, wie man den Ersthelfer zurücktritt.
Justin Kredible

2
und wo sollen wir diese self.view endEditing nennen?
user4951

8
Es stimmt, dass dies die Frage nicht genau beantwortet, aber es ist eindeutig eine sehr nützliche Antwort. Vielen Dank!
NovaJoe

6
+1, auch wenn es keine Antwort auf die Frage ist. Warum? Weil viele mit einer Frage hierher kommen, die diese Antwort beantwortet :) Mindestens 267 (im Moment) von ihnen ...
Rok Jarc

1
Dies beantwortet die Frage nicht.
Sasho

186

Eine übliche Methode zur Manipulation des Ersthelfers besteht darin, keine gezielten Aktionen auszuführen. Dies ist eine Möglichkeit, eine beliebige Nachricht an die Antwortkette zu senden (beginnend mit dem ersten Antwortenden) und die Kette entlang fortzufahren, bis jemand auf die Nachricht antwortet (hat eine Methode implementiert, die dem Selektor entspricht).

Für den Fall, dass die Tastatur geschlossen wird, ist dies die effektivste Methode, die unabhängig vom Fenster oder der Ansicht als Ersthelfer funktioniert:

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

Dies sollte effektiver sein als gerade [self.view.window endEditing:YES].

(Danke an BigZaphod , der mich an das Konzept erinnert hat)


4
Das ist so cool. Möglicherweise der sauberste Cocoa Touch-Trick, den ich bisher auf SO gesehen habe. Hat mir ohne Ende geholfen!
mluisbrown

Dies ist bei weitem die beste Lösung, danke eine Million! Dies ist viel besser als die obige Lösung, da es sogar funktioniert, wenn das aktive Textfeld Teil der Zusatzansicht der Tastatur ist (in diesem Fall ist es nicht Teil unserer Ansichtshierarchie)
Pizzaiola Gorgonzola

2
Diese Lösung ist die Best-Practice-Lösung. Das System weiß bereits, wer der Ersthelfer ist, es muss nicht gefunden werden.
Leftspin

2
Ich möchte dieser Antwort mehr als eine Gegenstimme geben. Das ist verdammt genial.
Reid Main

1
devios1: Jakobs Antwort ist genau das, was die Frage verlangt, aber es ist ein Hack über dem Responder-System, anstatt das Responder-System so zu verwenden, wie es entworfen wurde. Dies ist sauberer und erreicht das, wofür die meisten Menschen zu dieser Frage kommen, und auch in einem Einzeiler. (Sie können natürlich jede Nachricht im Stil einer Zielaktion senden und nicht nur resignFirstResponder).
Nevyn

98

Hier ist eine Kategorie, mit der Sie schnell den Ersthelfer finden können, indem Sie anrufen [UIResponder currentFirstResponder]. Fügen Sie Ihrem Projekt einfach die folgenden zwei Dateien hinzu:

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

Der Trick dabei ist, dass das Senden einer Aktion an nil sie an den Ersthelfer sendet.

(Ich habe diese Antwort ursprünglich hier veröffentlicht: https://stackoverflow.com/a/14135456/322427 )


1
+1 Dies ist die eleganteste Lösung für das Problem (und die tatsächlich gestellte Frage), die ich bisher gesehen habe. Wiederverwendbar und effizient! Stimmen Sie dieser Antwort zu!
Devios1

3
Bravo. Dies könnte der schönste und sauberste Hack sein, den ich je für Objc gesehen habe ...
Aviel Gross

Sehr schön. Sollte wirklich gut funktionieren, um dann den Ersthelfer fragen zu können, wer (wenn überhaupt) in der Kette eine bestimmte Aktionsmethode handhabt, wenn sie gesendet werden soll. Sie können dies verwenden, um Steuerelemente zu aktivieren oder zu deaktivieren, je nachdem, ob sie verarbeitet werden können. Ich werde meine eigene Frage dazu beantworten und auf Ihre Antwort verweisen.
Benjohn


Warnung: Dies funktioniert nicht, wenn Sie mehrere Fenster hinzufügen. Ich kann leider noch nicht auf eine darüber hinausgehende Ursache hinweisen.
Heath Borders

41

Hier ist eine in Swift implementierte Erweiterung, die auf Jakob Eggers hervorragender Antwort basiert:

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

1
Geänderte Antwort auf für swift1.2, wo statische Variablen unterstützt werden.
Nacho4d

Hallo, wenn ich das Ergebnis in einer brandneuen Single-View-Anwendung in viewDidAppear () ausdrucke, bekomme ich null. Sollte die Ansicht nicht der Ersthelfer sein?
Farhadf

@LinusGeffarth Ist es nicht sinnvoller, die statische Methode currentanstelle von aufzurufen first?
Code Commander

Ratet mal, bearbeitet es. Ich habe anscheinend den wichtigen Teil fallen gelassen, als ich den Variablennamen abgekürzt habe.
LinusGeffarth

29

Es ist nicht schön, aber wie ich den ersten Antwortgeber zurücktrete, wenn ich nicht weiß, was der Antwortende ist:

Erstellen Sie ein UITextField, entweder in IB oder programmgesteuert. Mach es versteckt. Verknüpfen Sie es mit Ihrem Code, wenn Sie es in IB gemacht haben.

Wenn Sie dann die Tastatur schließen möchten, schalten Sie den Responder in das unsichtbare Textfeld und geben es sofort auf:

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

61
Das ist sowohl schrecklich als auch genial zugleich. Nett.
Alex Wayne

4
Ich finde das ein bisschen gefährlich - Apple könnte eines Tages entscheiden, dass es nicht beabsichtigt ist, ein verstecktes Steuerelement zum Ersthelfer zu machen, und iOS "reparieren", dies nicht mehr zu tun, und dann funktioniert Ihr Trick möglicherweise nicht mehr.
Thomas Tempelmann

2
Oder Sie können den firstResponder einem UITextField zuweisen, das derzeit sichtbar ist, und dann resignFirstResponder für dieses bestimmte textField aufrufen. Dadurch entfällt die Notwendigkeit, eine ausgeblendete Ansicht zu erstellen. Trotzdem +1.
Swifty McSwifterton

3
Ich denke eher, dass es nur schrecklich ist ... es kann das Tastaturlayout (oder die Eingabeansicht) ändern, bevor es ausgeblendet wird
Daniel

19

Für eine Swift 3 & 4-Version von Nevyns Antwort:

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

10

Hier ist eine Lösung, die den richtigen Ersthelfer meldet (viele andere Lösungen melden beispielsweise keinen UIViewControllerals Ersthelfer), keine Schleife über die Ansichtshierarchie erfordert und keine privaten APIs verwendet.

Es nutzt Apples Methode sendAction: to: from: forEvent:, die bereits weiß, wie man auf den Ersthelfer zugreift .

Wir müssen es nur auf zwei Arten optimieren:

  • Erweitern UIResponderSie, damit unser eigener Code auf dem Ersthelfer ausgeführt werden kann.
  • Unterklasse UIEvent, um den Ersthelfer zurückzugeben.

Hier ist der 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

7

Der Ersthelfer kann eine beliebige Instanz der Klasse UIResponder sein, daher gibt es andere Klassen, die trotz der UIViews möglicherweise der Ersthelfer sind. Zum Beispiel UIViewControllerkönnte auch der Ersthelfer sein.

In dieser Übersicht finden Sie einen rekursiven Weg, um den Ersthelfer zu erhalten, indem Sie die Hierarchie der Controller ausgehend vom rootViewController der Anwendungsfenster durchlaufen.

Sie können dann den Ersthelfer abrufen, indem Sie dies tun

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

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

Wenn der Ersthelfer jedoch keine Unterklasse von UIView oder UIViewController ist, schlägt dieser Ansatz fehl.

Um dieses Problem zu beheben, können wir einen anderen Ansatz UIResponderwählen, indem wir eine Kategorie erstellen und ein magisches Swizzeling durchführen, um ein Array aller lebenden Instanzen dieser Klasse erstellen zu können. Um den Ersthelfer zu erhalten, können wir einfach iterieren und jedes Objekt fragen, ob -isFirstResponder.

Dieser Ansatz ist in diesem anderen Kern implementiert .

Ich hoffe es hilft.


7

Mit Swift und einem bestimmten UIViewObjekt kann dies helfen:

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
}

Legen Sie es einfach in Ihre UIViewControllerund verwenden Sie es wie folgt:

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

Beachten Sie, dass das Ergebnis ein optionaler Wert ist, sodass er null ist, falls in den Unteransichten der angegebenen Ansichten kein firstResponser gefunden wurde.


5

Durchlaufen Sie die Ansichten, die der Ersthelfer sein könnten, und verwenden Sie sie, - (BOOL)isFirstResponderum festzustellen, ob sie aktuell sind.


4

Peter Steinberger hat gerade über die private Benachrichtigung getwittertUIWindowFirstResponderDidChangeNotification , die Sie beobachten können, wenn Sie die Änderung von firstResponder beobachten möchten.


Er wird abgelehnt, weil die Verwendung einer privaten API, vielleicht auch die Verwendung einer privaten Benachrichtigung, eine schlechte Idee sein könnte ...
Laszlo

4

Wenn Sie nur die Tastatur ausschalten müssen, wenn der Benutzer auf einen Hintergrundbereich tippt, fügen Sie einen Gestenerkenner hinzu und senden Sie die [[self view] endEditing:YES]Nachricht damit.

Sie können den Tap-Gestenerkenner in die xib- oder Storyboard-Datei einfügen und mit einer Aktion verbinden.

sieht so aus dann fertig

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

Das hat bei mir super funktioniert. Ich habe eine Verbindung zu einer Ansicht auf dem Xib ausgewählt und kann zusätzliche Ansichten hinzufügen, die in Referenzieren von Outlet-Sammlungen
aufgerufen werden sollen

4

Genau hier ist die Swift-Version des fantastischen Ansatzes von 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
    }

}

3

Folgendes habe ich getan, um herauszufinden, welcher UITextField der erste Antwortende ist, wenn der Benutzer in einem ModalViewController auf Speichern / Abbrechen klickt:

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

            }
        }

    }

}

2

Dies ist, was ich in meiner UIViewController-Kategorie habe. Nützlich für viele Dinge, einschließlich Ersthelfer. Blöcke sind großartig!

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


2

Sie können die folgende UIViewErweiterung auswählen , um sie zu erhalten (Gutschrift von Daniel) :

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

1

Sie können es auch so versuchen:

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

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

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

Ich habe es nicht ausprobiert, aber es scheint eine gute Lösung zu sein


1

Die Lösung von Romeo https://stackoverflow.com/a/2799675/661022 ist cool, aber ich habe festgestellt, dass der Code eine weitere Schleife benötigt. Ich habe mit tableViewController gearbeitet. Ich habe das Skript bearbeitet und dann überprüft. Alles hat perfekt funktioniert.

Ich habe empfohlen, dies zu versuchen:

- (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]);
                        }
                    }
                }
            }
        }
    }
}

Vielen Dank! Sie haben Recht, die meisten hier vorgestellten Antworten sind keine Lösung, wenn Sie mit uitableview arbeiten. Sie können Ihren Code if ([textField isKindOfClass:[UITextField class]])erheblich vereinfachen, sogar das if entfernen, die Verwendung für uitextview zulassen, und den Erstantwortenden zurückgeben.
Frade

1

Dies ist ein guter Kandidat für eine Rekursion! UIView muss keine Kategorie hinzugefügt werden.

Verwendung (von Ihrem View Controller):

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

1

Sie können privite API wie folgt nennen, Apple ignorieren:

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

1
aber ... bitte benutze 'respondsToSelector' um zu überprüfen
ibcker

Ich überprüfe jetzt 'respondsToSelector', erhalte aber immer noch eine Warnung, dies könnte unsicher sein. Kann ich die Warnung irgendwie loswerden?
ecth

1

Schnelle Version von @ thomas-müllers Antwort

extension UIView {

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

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

        return nil
    }

}

1

Ich habe einen etwas anderen Ansatz als die meisten hier. Anstatt die Sammlung von Ansichten zu durchlaufen und nach der eingestellten zu suchen isFirstResponder, sende ich auch eine Nachricht an nil, speichere aber den Empfänger (falls vorhanden) und gebe diesen Wert zurück, damit der Anrufer die tatsächliche Instanz erhält (erneut, falls vorhanden). .

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

Ich kann dann den Ersthelfer, falls vorhanden, zurücktreten, indem ich dies tue ...

return UIResponder.first?.resignFirstResponder()

Aber das kann ich jetzt auch ...

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

1

Ich möchte Ihnen meine Implementierung für die Suche nach Ersthelfern in UIView mitteilen. Ich hoffe es hilft und entschuldige mich für mein Englisch. Vielen Dank

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

0

Code unter Arbeit.

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

0

Update: Ich habe mich geirrt. Sie können in der Tat UIApplication.shared.sendAction(_:to:from:for:)den in diesem Link gezeigten Ersthelfer anrufen: http://stackoverflow.com/a/14135456/746890 .


Die meisten Antworten hier können den aktuellen Ersthelfer nicht wirklich finden, wenn er nicht in der Ansichtshierarchie enthalten ist. Zum Beispiel AppDelegateoder UIViewControllerUnterklassen.

Es gibt eine Möglichkeit, sicherzustellen, dass Sie es finden, auch wenn das Ersthelferobjekt kein a ist UIView.

Lassen Sie uns zunächst eine umgekehrte Version davon implementieren, indem Sie die nextEigenschaft von UIResponder:

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

Mit dieser berechneten Eigenschaft können wir den aktuellen Ersthelfer von unten nach oben finden, auch wenn dies nicht der Fall ist UIView. Zum Beispiel von a viewnachUIViewController wer es verwaltet, wenn der View Controller der Ersthelfer ist.

Wir benötigen jedoch immer noch eine Top-Down-Auflösung, eine einzige var, um den aktuellen Ersthelfer zu erhalten.

Zunächst mit der Ansichtshierarchie:

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

Dadurch wird der Ersthelfer rückwärts gesucht, und wenn er nicht gefunden werden kann, werden seine Unteransichten angewiesen, dasselbe zu tun (da die Unteransichten nextnicht unbedingt selbst sind). Damit können wir es aus jeder Sicht finden, auch UIWindow.

Und schließlich können wir dies bauen:

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

Wenn Sie also den Ersthelfer abrufen möchten, können Sie anrufen:

let firstResponder = UIResponder.first

0

Einfachster Weg, um einen Ersthelfer zu finden:

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

Die Standardimplementierung sendet die Aktionsmethode an das angegebene Zielobjekt oder, falls kein Ziel angegeben ist, an den Ersthelfer.

Nächster Schritt:

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

Verwendung: Holen Sie sich einfach UIResponder.actualFirstfür Ihre eigenen Zwecke.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.