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

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

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

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

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


当前回答

更新:我错了。你确实可以使用UIApplication.shared.sendAction(_:to:from:for:)来调用这个链接中演示的第一个响应器:http://stackoverflow.com/a/14135456/746890。


如果当前的第一响应器不在视图层次结构中,那么这里的大多数答案都不能真正找到它。例如,AppDelegate或UIViewController子类。

有一种方法可以保证你找到它,即使第一个responder对象不是UIView。

首先让我们实现一个相反的版本,使用UIResponder的下一个属性:

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

有了这个computed属性,我们可以从下到上找到当前的第一响应器,即使它不是UIView。例如,从一个视图到管理它的UIViewController,如果视图控制器是第一响应器。

然而,我们仍然需要一个自顶向下的解决方案,一个单独的变量来获得当前的第一个响应器。

首先是视图层次结构:

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

这将反向搜索第一个响应器,如果它找不到它,它会告诉它的子视图做同样的事情(因为它的子视图的next不一定是它自己)。有了这个,我们可以从任何视图中找到它,包括UIWindow。

最后,我们可以建立这个:

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

所以当你想检索第一个响应器时,你可以调用:

let firstResponder = UIResponder.first

其他回答

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

遍历可能是第一响应器的视图,并使用- (BOOL)isFirstResponder来确定它们当前是否是。

我想和你们分享我的实现,在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;
}

第一响应者可以是类UIResponder的任何实例,所以有其他类可能是第一响应者,尽管有UIViews。例如,UIViewController也可能是第一响应器。

在这个要点中,您将找到一种递归方法,通过从应用程序窗口的rootViewController开始循环遍历控制器层次结构来获得第一个响应器。

你可以通过这样来检索第一个响应器

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

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

然而,如果第一响应器不是UIView或UIViewController的子类,这种方法将失败。

为了解决这个问题,我们可以采用不同的方法,在UIResponder上创建一个类别,并执行一些神奇的旋转,以便能够构建一个包含该类所有活实例的数组。然后,为了获得第一个responder,我们可以简单地迭代并询问每个对象是否-isFirstResponder。

这种方法可以在另一个要点中找到实现。

希望能有所帮助。

对于Swift 3和4版本的nevyn的回答:

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