我在一个多星期前提交了应用,今天收到了可怕的拒绝邮件。它告诉我,我的应用程序不能被接受,因为我使用的是非公共API;具体来说,它说,

应用程序中包含的非公共API是firstResponder。

现在,违规的API调用实际上是一个解决方案,我发现这里的SO:

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

如何在屏幕上显示当前的第一响应器?我正在寻找一种不会让我的应用程序被拒绝的方法。


当前回答

而不是遍历视图集合寻找设置了isFirstResponder的视图,我也将消息发送给nil,但我存储了消息的接收器,以便我可以返回它并对它做任何我想做的事情。

此外,我将在调用本身的deferred语句中保存找到的responder的optional归零。这确保在调用结束时没有引用保留——即使是弱引用。

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 and place it in the _foundFirstResponder variable.
        // As such, the variable will contain the current first responder (if any) immediately after this line executes
        UIApplication.shared.sendAction(#selector(UIResponder.storeFirstResponder(_:)), to: nil, from: nil, for: nil)

        // The following 'defer' statement runs *after* this getter returns,
        // thus releasing any strong reference held by the variable immediately thereafter
        defer {
            _foundFirstResponder = nil
        }

        // Return the found first-responder (if any) back to the caller
        return _foundFirstResponder
    }

    // Make sure to mark this with '@objc' since it has to be reachable as a selector for `sendAction`
    @objc func storeFirstResponder(_ sender: AnyObject) {

        // Capture the recipient of this message (self), which is the first responder
        _foundFirstResponder = self
    }
}

有了上面的,我可以通过简单地这样做辞去第一响应者…

UIResponder.first?.resignFirstResponder()

但由于我的API实际上交还了第一个responder是什么,我可以对它做任何我想做的事情。

下面是一个示例,它检查当前的第一个响应器是否是一个带有helpMessage属性设置的UITextField,如果是,则在控件旁边的帮助气泡中显示它。我们通过屏幕上的“Quick Help”按钮来调用它。

func showQuickHelp(){

    if let textField = UIResponder?.first as? UITextField,
       let helpMessage = textField.helpMessage {
    
        textField.showHelpBubble(with:helpMessage)
    }
}

对上述的支持在UITextField上的扩展中定义,就像这样…

extension UITextField {
    var helpMessage:String? { ... }
    func showHelpBubble(with message:String) { ... }
}

现在要支持这个功能,我们所要做的就是决定哪些文本字段有帮助消息,UI会为我们处理其余的事情。

其他回答

找到第一响应者的最简单方法:

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

默认实现将动作方法分派给给定对象 目标对象,如果没有指定目标,则发送给第一个响应器。

下一步:

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

用法: 只需要获取UIResponder。首先是为了你自己的目的。

下面是一个报告正确的第一响应器的解决方案(例如,许多其他解决方案不会报告UIViewController作为第一响应器),不需要在视图层次结构上循环,也不使用私有api。

它利用了苹果的sendAction:to:from:forEvent:方法,该方法已经知道如何访问第一个响应器。

我们只需要在2个方面进行调整:

扩展UIResponder,这样它就可以在第一个responder上执行我们自己的代码。 子类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

如果你的最终目标只是辞职第一个响应者,这应该工作:[self。视图endEditing:是的)

这是我在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;
 }];
}

下面的代码可以工作。

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