الحصول على الحالية المستجيب الأول دون استخدام API الخاص

StackOverflow https://stackoverflow.com/questions/1823317

سؤال

قدمت بلدي التطبيق قليلا قبل أكثر من أسبوع وحصلت اللعين رفض البريد الإلكتروني اليوم.يقول لي أن بلدي التطبيق لا يمكن قبوله لأن أنا باستخدام API غير عامة;على وجه التحديد, يقول ،

غير العامة API التي يتم تضمينها في التطبيق الخاص بك هو firstResponder.

الآن المخالف استدعاء API هو في الواقع حل وجدت هنا على ذلك:

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

كيف يمكنني الحصول على الحالية المستجيب الأول على الشاشة ؟ أنا أبحث عن الطريقة التي لن تحصل على بلدي التطبيق رفض.

هل كانت مفيدة؟

المحلول

في واحدة من بلدي تطبيقات وكثيرا ما تريد المستجيب الأول إلى الاستقالة إذا كان المستخدم الصنابير على الخلفية.لهذا الغرض كتبت الفئة على UIView التي أدعو UIWindow.

ما يلي هو بناء على ذلك يجب أن تعود المستجيب الأول.

@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

دائرة الرقابة الداخلية 7+

- (id)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;
    }
    for (UIView *subView in self.view.subviews) {
        if ([subView isFirstResponder]) {
            return subView;
        }
    }
    return nil;
}

سويفت:

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

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

        return nil
    }
}

مثال للاستخدام في سويفت:

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

نصائح أخرى

وإذا كان هدفك النهائي هو فقط لتقديم استقالته المستجيب الأول، وهذا ينبغي العمل: [self.view endEditing:YES]

وهناك طريقة شائعة من التلاعب المستجيب الأول هو استخدام شيء الإجراءات المستهدفة. هذا هو وسيلة لإرسال رسالة التعسفية لسلسلة الرد (بدءا المستجيب الأول)، واستمرار أسفل السلسلة حتى يستجيب شخص ما إلى الرسالة (نفذت طريقة مطابقة محدد).

لحالة طرد لوحة المفاتيح، هذه هي الطريقة الأكثر فعالية التي ستعمل بغض النظر عن أي نافذة أو الرأي هو المستجيب الأول:

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

وهذا ينبغي أن يكون أكثر فعالية من حتى [self.view.window endEditing:YES].

و(بفضل BigZaphod للحصول على تذكيري مفهوم)

وهنا الفئة التي يسمح لك العثور بسرعة على المستجيب الأول عن طريق استدعاء [UIResponder currentFirstResponder].فقط إضافة التاليين الملفات إلى المشروع الخاص بك:

UIResponder+FirstResponder.ح

#import <Cocoa/Cocoa.h>
@interface UIResponder (FirstResponder)
    +(id)currentFirstResponder;
@end

UIResponder+FirstResponder.م

#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

الحيلة هنا هي أن إرسال العمل إلى النيل ويرسله إلى المستجيب الأول.

(أصلا أنا نشرت هذا الجواب هنا: https://stackoverflow.com/a/14135456/322427)

وهنا لتمديد تنفيذها في سويفت على أساس الإجابة اكثر من ممتاز جاكوب الرقيب في:

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

سويفت 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
    }
}

وانها ليست جميلة، ولكن الطريقة I الاستقالة من firstResponder عندما كنت لا أعرف ما أن الرد هو:

وإنشاء UITextField، سواء في IB أو برمجيا. جعلها المخفية. ربط ليصل إلى التعليمات البرمجية الخاصة بك إذا قمت بها في IB.

وبعد ذلك، عندما تريد فصل لوحة المفاتيح، يمكنك التبديل المستجيب لحقل النص غير مرئية، والاستقالة على الفور:

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

من أجل الإسراع 3 & 4 إصدار nevyn هو الجواب:

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

هنا هو الحل الذي التقارير الصحيحة المستجيب الأول (العديد من الحلول الأخرى لا تقرير UIViewController كما المستجيب الأول ، على سبيل المثال), لا يتطلب حلقات على عرض التسلسل الهرمي ، و لا تستخدم واجهات برمجة التطبيقات الخاصة.

فإنه يستفيد من أبل طريقة sendAction:إلى:من:forEvent:, الذي يعرف بالفعل كيفية الوصول المستجيب الأول.

نحن بحاجة فقط إلى قرص عليه في 2 طرق:

  • تمديد UIResponder لذلك يمكن تنفيذ منطقتنا رمز على المستجيب الأول.
  • فرعية UIEvent من أجل العودة إلى المستجيب الأول.

هنا هو رمز:

@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

والمستجيب الأول يمكن أن يكون أي مثيل UIResponder الطبقة، لذلك هناك فئات أخرى قد يكون المستجيب الأول على الرغم من UIViews. على سبيل المثال UIViewController قد يكون أيضا المستجيب الأول.

في هذا جوهر الذي سوف نجد طريقة متكررة للحصول على المستجيب الأول بواسطة حلقات من خلال التسلسل الهرمي وحدات تحكم بدءا من rootViewController من نوافذ التطبيق.

ويمكنك استرداد ثم المستجيب الأول عن طريق القيام

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

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

ولكن، إذا كان المستجيب الأول ليست فئة فرعية من UIView أو UIViewController، ستفشل هذا النهج.

لإصلاح هذه المشكلة يمكن أن نفعله نهجا مختلفا عن طريق إنشاء الفئة على UIResponder وأداء بعض swizzeling السحر لتكون قادرة على بناء مجموعة من جميع حالات الذين يعيشون من هذه الفئة. ثم، للحصول على المستجيب الأول نستطيع أعاد بسيطة واطلب من كل كائن إذا -isFirstResponder.

وهذا النهج يمكن العثور عليها في تنفيذ هذا جوهر البعض.

ونأمل أن يساعد.

باستخدام سويفت و مع محددة UIView وجوه هذه قد تساعد:

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
}

فقط ضع هذا في UIViewController واستخدامه مثل هذا:

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

علما أن النتيجة هي اختياري القيمة لذلك سوف يكون صفر في حالة عدم firstResponser وجد في وجهات النظر subviews.

وتكرار عبر وجهات النظر التي يمكن أن يكون المستجيب الأول واستخدام - (BOOL)isFirstResponder لتحديد ما إذا كانت حاليا.

بالتغريد عن UIWindowFirstResponderDidChangeNotification الإخطار الخاص، والتي يمكن أن نلاحظ إذا كنت تريد مشاهدة التغيير firstResponder.

إذا كنت بحاجة فقط لقتل لوحة المفاتيح عند نقر المستخدم على المنطقة الخلفية لماذا لا تضيف إلى التعرف على لفتة واستخدامه لإرسال رسالة [[self view] endEditing:YES]؟

ويمكنك إضافة recogniser الحنفية فتة في ملف xib أو القصة المصورة وتوصيله إلى العمل،

ويبدو شيئا من هذا القبيل الانتهاء ثم

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

وفقط هو الحال هنا هو الإصدار سريع من نهج رهيبة جاكوب الرقيب في:

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
    }

}

وهذا هو ما فعلته للعثور على ما هو UITextField وfirstResponder عندما ينقر المستخدم حفظ / الغاء في 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];
                }

            }
        }

    }

}

وهذا هو ما قلته في بلدي UIViewController الفئة. مفيد لأشياء كثيرة، بما في ذلك الحصول على المستجيب الأول. كتل كبيرة!

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

ومع فئة على UIResponder، فمن الممكن أن نسأل من الناحية القانونية الكائن UIApplication أن أقول لكم من هو المستجيب الأول.

وانظر هذا:

<وأ href = "https://stackoverflow.com/questions/5029267/is-there-any-way-of-asking-an-ios-view-which-of-its-children-has-first- الرد "> هل هناك أي طريقة لطلب وجهة نظر دائرة الرقابة الداخلية التي من أبنائها لها وضع المستجيب الأول؟

ويمكنك اختيار تمديد UIView التالية للحصول عليه <م> (الائتمان عن طريق دانيال) :

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

ويمكنك أيضا محاولة من هذا القبيل:

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

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

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

وأنا لم يحاول ذلك، ولكن يبدو حلا جيدا

والحل من روميو https://stackoverflow.com/a/2799675/661022 هو بارد، لكني لاحظت أن رمز يحتاج أكثر واحد حلقة. كنت أعمل مع tableViewController. أنا بتحرير النصي ثم راجعت. كل شيء عملت الكمال.

وأنا ريكوميد لمحاولة هذا:

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

وهذا هو مرشح جيد لالعودية! لا حاجة لإضافة فئة لUIView.

والاستخدام (من وحدة تحكم وجهة نظركم):

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

والرمز:

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

ويمكنك استدعاء API privite مثل هذا، والتفاح تجاهل:

<اقتباس فقرة>
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];

وأود أن مشاركتها معك تنفيذ بلدي لتجد المستجيب الأول في أي مكان من UIView. وآمل أن يساعد وآسف لغتي الإنجليزية. بفضل

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

و}

ونسخة سويفت من 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
    }

}

ولدي مقاربة مختلفة قليلا من معظم هنا. بدلا من تكرار خلال مجموعة من وجهات النظر تبحث عن واحدة لديها مجموعة isFirstResponder، وأنا أيضا إرسال رسالة إلى nil، لكنني تخزين المتلقي (إن وجدت)، ثم العودة أن القيمة بحيث يحصل الطالب على سبيل المثال الفعلي (مرة أخرى، إذا وجدت).

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

وبعد ذلك يمكنني أن يستقيل المستجيب الأول، إن وجدت، من خلال ذلك ...

return UIResponder.first?.resignFirstResponder()

ولكن يمكنني الآن أن تفعل هذا ...

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

ورمز أدناه العمل.

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

تحديث:كنت على خطأ.يمكنك بالفعل استخدام UIApplication.shared.sendAction(_:to:from:for:) للاتصال المستجيب الأول هو موضح في هذا الرابط: http://stackoverflow.com/a/14135456/746890.


معظم الإجابات هنا لا يمكن أن تجد حقا الحالي المستجيب الأول إذا لم يكن في عرض التسلسل الهرمي.على سبيل المثال ، AppDelegate أو UIViewController فرعية.

هناك طريقة تضمن لك أن تجد أنه حتى لو كان المستجيب الأول كائن ليس UIView.

أولا يتيح تنفيذ عكس نسخة منه ، وذلك باستخدام next ملك UIResponder:

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

مع هذا محسوب الملكية ، يمكن أن نجد الحالية المستجيب الأول من أسفل إلى أعلى حتى إذا لم يكن UIView.على سبيل المثال ، من view إلى UIViewController من إدارة ذلك ، إذا كان عرض وحدة تحكم هو المستجيب الأول.

إلا أننا لا نزال بحاجة إلى من أعلى إلى أسفل قرار واحد var للحصول على الحالية المستجيب الأول.

الأولى مع عرض التسلسل الهرمي:

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

هذا بحث عن المستجيب الأول إلى الوراء, و إذا كان لا يمكن العثور عليه ، فإنه أخبر به subviews أن تفعل الشيء نفسه (لأن subview هو next ليس بالضرورة نفسها).مع هذا نجد من أي عرض ، بما في ذلك UIWindow.

و أخيرا يمكننا أن نبني هذا:

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

حتى عندما كنت ترغب في استرداد المستجيب الأول ، يمكنك الاتصال على:

let firstResponder = UIResponder.first
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top