Frage

Ich habe meinen Antrag vor etwas mehr als einer Woche eingereicht und heute die gefürchtete Ablehnungs-E-Mail erhalten.Mir wird angezeigt, dass meine App nicht akzeptiert werden kann, da ich eine nicht öffentliche API verwende.Konkret heißt es:

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

Nun, der beleidigende API-Aufruf ist 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, die Ablehnung meiner App zu verhindern.

War es hilfreich?

Lösung

In einem meiner Anwendungen Ich möchte oft die erste Responder zurücktreten, wenn der Benutzer auf dem Hintergrund klopft. Zu diesem Zweck habe ich eine Kategorie auf UIView, die ich auf der UIWindow nennen.

Die folgende auf das basiert und soll die Ersthelfer zurück.

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

Anwendungsbeispiel in Swift:

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

Andere Tipps

Wenn Ihr Endziel nur ist der ersten Responder zum Rücktritt, sollte diese Arbeit: [self.view endEditing:YES]

Ein üblicher Weg, den Ersthelfer der Manipulation ist gleich Null gezielte Aktionen zu verwenden. Dies ist eine Art und Weise einer beliebigen Nachricht an den Responder sendet Kette (mit dem Ersthelfer beginnend), und weiterhin in der Kette, bis jemand auf die Nachricht reagiert (hat eine Methode Anpassung der Selektor implementiert).

Für den Fall der Verwendung der Tastatur zu entlassen, ist dies die effektivste Art und Weise, unabhängig davon, welches Fenster oder Ansicht funktioniert ist Ersthelfer:

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

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

(Danke an BigZaphod für mich der Begriff erinnert)

Hier ist eine Kategorie, die Sie durch den Aufruf [UIResponder currentFirstResponder] den Ersthelfer schnell finden. Fügen Sie einfach die folgenden zwei Dateien zu einem Projekt:

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

hier Der Trick besteht darin, dass eine Aktion auf Null zu senden sendet ihn an den Ersthelfer.

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

Hier ist eine Erweiterung in Swift implementiert basierend auf Jakob Egger trefflichsten Antwort:

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

Es ist nicht schön, aber die Art, wie ich die firstResponder zurücktreten, wenn ich weiß nicht, was das der Responder ist:

Erstellen Sie eine UITextField, entweder in IB oder programmatisch. Machen Sie es sich versteckte. Verknüpfen es mit Ihrem Code, wenn Sie es in IB gemacht.

Wenn Sie dann die Tastatur entlassen wollen, schalten Sie den Responder zu dem unsichtbaren Textfeld und sofort zurücktreten es:

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

Für eine Swift 3 & 4-Version von Nevyn des beantworten:

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

Hier ist eine Lösung, die die richtigen Ersthelfer berichtet (viele andere Lösungen keinen UIViewController als Ersthelfer berichten, zum Beispiel), erfordert nicht über die Ansichtshierarchie Looping und verwendet keine privaten APIs.

Es nutzt Apples Methode sendAction: bis: von: forEvent:. , der bereits weiß, wie die First Responder zugreifen

Wir müssen es nur auf 2 Arten zwicken:

  • Extend UIResponder so dass es unsere eigenen Code auf dem Ersthelfer ausführen kann.
  • Subclass UIEvent, um den ersten Responder zurückzukehren.

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

Der Ersthelfer kann jede Instanz der Klasse UIResponder sein, so gibt es weitere Klassen, die die Ersthelfer trotz der UIViews sein könnte. Zum Beispiel UIViewController könnte auch der erste Responder sein.

diesem Kern eine rekursive Art und Weise finden die Ersthelfer zu erhalten, indem Schleife durch die Hierarchie des Controller aus dem RootViewController der Windows-Anwendung zu starten.

Sie können abrufen dann den Ersthelfer, indem Sie

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

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

Wenn jedoch der Ersthelfer nicht eine Unterklasse von UIView oder UIViewController ist, wird dieser Ansatz scheitern.

Um dieses Problem zu beheben wir durch die Schaffung einer Kategorie auf UIResponder und führen Sie einige magische swizzeling in der Lage sein, einen anderen Ansatz tun ein Array aller lebenden Instanzen dieser Klasse zu bauen. Dann wird den ersten Responder bekommen wir einfach laufen können und fragt jedes Objekt, wenn -isFirstResponder.

Dieser Ansatz kann in dieser anderen Kern implementiert gefunden werden.

Hoffe, es hilft.

Mit Swift und mit spezifischem UIView Objekt Dies könnte 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
}

Einfach setzen Sie es in Ihrem UIViewController und verwenden Sie es wie folgt aus:

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

beachten Sie, dass das Ergebnis ist ein Optional Wert , damit es gleich Null in Fall sein wird kein firstResponser in der gegebenen Ansichten Subviews gefunden wurde.

über die Ansichten Iterate, dass die Ersthelfer sein könnte und verwenden - (BOOL)isFirstResponder, um zu bestimmen, ob sie sich gerade befinden.

Peter Steinberger nur getwittert über die private Mitteilung UIWindowFirstResponderDidChangeNotification, die Sie beobachten können, wenn Sie wollen beobachten Sie die firstResponder ändern.

Wenn Sie nur die Tastatur deaktivieren müssen, wenn der Benutzer auf einen Hintergrundbereich tippt, können Sie eine Gestenerkennung hinzufügen und diese zum Senden verwenden [[self view] endEditing:YES] Nachricht?

Sie können den Tap-Gestenerkenner in der XIB- oder Storyboard-Datei hinzufügen und ihn mit einer Aktion verbinden.

sieht dann in etwa so aus, fertig

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

Just es Fall hier ist Swift Version von awesome Jakob Egger Ansatz:

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
    }

}

Das ist das, was ich tat, zu finden, was UITextField die firstResponder ist, wenn der Benutzer auf Speichern klickt / Abbrechen in einem 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];
                }

            }
        }

    }

}

Dies ist, was ich in meiner UIViewController Kategorie habe. Nützlich für viele Dinge, erstversorger einschließlich bekommen. Die Blöcke sind toll!

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

Mit einer Kategorie auf UIResponder, ist es möglich, gesetzlich das UIApplication Objekt zu bitten, Ihnen zu sagen, wer der Ersthelfer ist.

Siehe dazu:

gibt es eine Möglichkeit, um eine iOS Ansicht zu fragen?

Sie können folgende UIView Erweiterung wählen, um es (Kredit von Daniel) :

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

Sie können auch versuchen, wie folgt aus:

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

Die Lösung von romeo https://stackoverflow.com/a/2799675/661022 ist cool, aber ich merkte, dass der Code braucht man mehr Loop. Ich arbeitete mit Tableviewcontroller. Ich habe das Skript und dann habe ich gecheckt. Alles funktionierte perfekt.

Ich recommed dies 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]);
                        }
                    }
                }
            }
        }
    }
}

Dies ist guter Kandidat für die Rekursion! Keine Notwendigkeit, eine Kategorie zu UIView hinzuzufügen.

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

Sie können privite api wie folgt aufrufen, Apfel ignorieren:

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

Ich möchte mit Ihnen meine Implementierung geteilt für Ersthelfer in überall von UIView zu finden. Ich hoffe, es hilft und sorry für mein Englisch. 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;

}

Swift-Version von @Thomas -Müller 's 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
    }

}

Ich habe einen etwas anderen Ansatz als die meisten hier. Anstatt durch die Sammlung von Ansichten iterieren für eine suchen, die isFirstResponder Satz hat auch ich eine Nachricht senden nil, aber ich speichere den Empfänger (falls vorhanden), dann diesen Wert zurück, so der Anrufer die eigentliche Instanz wird (wieder, wenn 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 zurücktreten, wenn überhaupt, durch dies zu tun ...

return UIResponder.first?.resignFirstResponder()

Aber ich kann jetzt auch tun ...

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

-Code unten zu arbeiten.

- (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: Ich war falsch. Sie können in der Tat UIApplication.shared.sendAction(_:to:from:for:) verwenden, um die Ersthelfer in diesem Link demonstriert zu nennen: http://stackoverflow.com/a/14135456/746890.


Die meisten Antworten hier kann wirklich nicht die aktuellen Ersthelfer finden, wenn es nicht in der Ansichtshierarchie ist. Zum Beispiel AppDelegate oder UIViewController Subklassen.

Es gibt einen Weg zu garantieren Ihr es zu finden, auch wenn das erste Responder Objekt kein UIView ist.

Zuerst eine umgekehrte Version davon läßt implementieren, die next Eigenschaft UIResponder mit:

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

Mit dieser berechneten Eigenschaft können wir die aktuellen Ersthelfer von unten finden sogar nach oben wenn es nicht UIView. Zum Beispiel von einem view zum UIViewController, die es verwalten, wenn der View-Controller der erste Responder ist.

Wir brauchen aber noch eine Top-down-Auflösung, einen einzelnen var den aktuellen Ersthelfer zu erhalten.

Zuerst mit der Ansichtshierarchie:

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

Dies wird für den Ersthelfer sucht nach hinten, und wenn es sich nicht finden kann, würde es seine Subviews sagen, das Gleiche zu tun (weil seine subview des next nicht unbedingt selbst). Damit können wir es aus einem beliebigen Ansicht finden, einschließlich UIWindow.

Und schließlich können wir diese 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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top