Получить текущий первый респондент без использования частного API - PullRequest
328 голосов
/ 01 декабря 2009

Я отправил свое приложение чуть более недели назад и получил страшное письмо с отказом сегодня. Это говорит мне, что мое приложение не может быть принято, потому что я использую непубличный API; в частности, это говорит,

Непубличный API, включенный в ваше приложение, это firstResponder.

Теперь вызывающий вызов API - это фактически решение, которое я нашел здесь в SO:

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

Как вывести текущего первого респондента на экран? Я ищу способ, которым мое приложение не будет отклонено.

Ответы [ 27 ]

528 голосов
/ 22 мая 2010

Если вашей конечной целью является просто отставка первого респондента, это должно сработать: [self.view endEditing:YES]

326 голосов
/ 01 декабря 2009

В одном из моих приложений я часто хочу, чтобы первый респондент подал в отставку, если пользователь нажимает на фон. Для этого я написал в 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

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

Пример использования в Swift:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}
181 голосов
/ 02 августа 2012

Обычный способ манипулирования первым респондентом - использовать целевые действия с нулевым значением. Это способ отправки произвольного сообщения в цепочку респондента (начиная с первого респондента) и продолжения вниз по цепочке, пока кто-то не ответит на сообщение (не реализовал метод, соответствующий селектору).

В случае отклонения клавиатуры это наиболее эффективный способ, который будет работать независимо от того, какое окно или представление является первым респондентом:

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

Это должно быть более эффективно, чем даже [self.view.window endEditing:YES].

(Спасибо BigZaphod за напоминание о концепции)

91 голосов
/ 24 января 2014

Вот категория, которая позволяет быстро найти первого респондента, позвонив по номеру [UIResponder currentFirstResponder]. Просто добавьте следующие два файла в ваш проект:

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

Хитрость в том, что отправка действия nil отправляет его первому ответчику.

(Я первоначально опубликовал этот ответ здесь: https://stackoverflow.com/a/14135456/322427)

35 голосов
/ 26 ноября 2014

Вот расширение, реализованное в Swift на основе самого превосходного ответа Якоба Эггера:

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
    }
}
30 голосов
/ 24 мая 2010

Это не красиво, но то, как я отказываюсь от первого ответа, когда я не знаю, что это за ответчик:

Создайте UITextField, либо в IB, либо программно. Сделать это скрытым Свяжите его с вашим кодом, если вы сделали это в IB.

Затем, когда вы хотите убрать клавиатуру, вы переключаете респондент в невидимое текстовое поле и сразу же оставляете его:

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
14 голосов
/ 18 января 2017

Для Swift 3 и 4 версии Невин ответ:

UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
10 голосов
/ 08 июля 2014

Вот решение, которое сообщает о правильном первом респонденте (например, многие другие решения не сообщат UIViewController в качестве первого респондента), не требует циклической обработки иерархии представления и не использует частные API .

Используется метод Apple sendAction: to: from: forEvent: , который уже знает, как получить доступ к первому респонденту.

Нам просто нужно настроить его двумя способами:

  • Расширьте 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
7 голосов
/ 11 мая 2012

Первым ответчиком может быть любой экземпляр класса UIResponder, поэтому существуют другие классы, которые могут быть первым ответчиком, несмотря на UIViews. Например, UIViewController также может быть первым респондентом.

В этой сущности вы найдете рекурсивный способ получения первого респондента, просматривая иерархию контроллеров начиная с rootViewController окон приложения.

Вы можете получить первого респондента, выполнив

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

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

Однако, если первый респондент не является подклассом UIView или UIViewController, этот подход завершится неудачей.

Чтобы решить эту проблему, мы можем использовать другой подход, создав категорию на UIResponder и выполнив некоторое волшебство, чтобы иметь возможность собрать массив всех живых экземпляров этого класса. Затем, чтобы получить первого респондента, мы можем просто повторить и спросить каждый объект, если -isFirstResponder.

Этот подход можно найти реализованным в этой другой сущности .

Надеюсь, это поможет.

6 голосов
/ 05 мая 2015

Использование Swift и с специфическим 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 не был найден в данных подпредставлениях просмотров.

...