objective-ccocoa-touchfirst-responder

Get the current first responder without using a private API


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.


Solution

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