Get the current first responder without using a pr

2018-12-31 07:16发布

I submitted my app a little over a week ago and got the dreaded rejection email today. It tells me that my app cannot be accepted because I'm using a non-public API; specifically, it says,

The non-public API that is included in your application is firstResponder.

Now, the offending API call is actually a solution I found here on SO:

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

How do I get the current first responder on the screen? I'm looking for a way that won't get my app rejected.

26条回答
弹指情弦暗扣
2楼-- · 2018-12-31 07:27

Swift version of @thomas-müller's response

extension UIView {

    func firstResponder() -> UIView? {
        if self.isFirstResponder() {
            return self
        }

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

        return nil
    }

}
查看更多
深知你不懂我心
3楼-- · 2018-12-31 07:28

In one of my applications I often want the first responder to resign if the user taps on the background. For this purpose I wrote a category on UIView, which I call on the UIWindow.

The following is based on that and should return the first responder.

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

Usage example in Swift:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}
查看更多
忆尘夕之涩
4楼-- · 2018-12-31 07:29

Code below work.

- (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;
}   
查看更多
有味是清欢
5楼-- · 2018-12-31 07:30

With a category on UIResponder, it is possible to legally ask the UIApplication object to tell you who the first responder is.

See this:

Is there any way of asking an iOS view which of its children has first responder status?

查看更多
零度萤火
6楼-- · 2018-12-31 07:30

You can try also like this:

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

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

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

I didn't try it but it seems a good solution

查看更多
查无此人
7楼-- · 2018-12-31 07:32

Here's a solution which reports the correct first responder (many other solutions won't report a UIViewController as the first responder, for example), doesn't require looping over the view hierarchy, and doesn't use private APIs.

It leverages Apple's method sendAction:to:from:forEvent:, which already knows how to access the first responder.

We just need to tweak it in 2 ways:

  • Extend UIResponder so it can execute our own code on the first responder.
  • Subclass UIEvent in order to return the first responder.

Here is the 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
查看更多
登录 后发表回答