Get the current first responder without using a private API

前端 未结 28 1831
夕颜
夕颜 2020-11-22 05:03

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; specif

相关标签:
28条回答
  • 2020-11-22 05:46

    A common way of manipulating the first responder is to use nil targeted actions. This is a way of sending an arbitrary message to the responder chain (starting with the first responder), and continuing down the chain until someone responds to the message (has implemented a method matching the selector).

    For the case of dismissing the keyboard, this is the most effective way that will work no matter which window or view is first responder:

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

    This should be more effective than even [self.view.window endEditing:YES].

    (Thanks to BigZaphod for reminding me of the concept)

    0 讨论(0)
  • 2020-11-22 05:46

    The solution from romeo https://stackoverflow.com/a/2799675/661022 is cool, but I noticed that the code needs one more loop. I was working with tableViewController. I edited the script and then I checked. Everything worked perfect.

    I recommed to try this:

    - (void)findFirstResponder
    {
        NSArray *subviews = [self.tableView subviews];
        for (id subv in subviews )
        {
            for (id cell in [subv subviews] ) {
                if ([cell isKindOfClass:[UITableViewCell class]])
                {
                    UITableViewCell *aCell = cell;
                    NSArray *cellContentViews = [[aCell contentView] subviews];
                    for (id textField in cellContentViews)
                    {
                        if ([textField isKindOfClass:[UITextField class]])
                        {
                            UITextField *theTextField = textField;
                            if ([theTextField isFirstResponder]) {
                                NSLog(@"current textField: %@", theTextField);
                                NSLog(@"current textFields's superview: %@", [theTextField superview]);
                            }
                        }
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:46

    Update: I was wrong. You can indeed use UIApplication.shared.sendAction(_:to:from:for:) to call the first responder demonstrated in this link: http://stackoverflow.com/a/14135456/746890.


    Most of the answers here can't really find the current first responder if it is not in the view hierarchy. For example, AppDelegate or UIViewController subclasses.

    There is a way to guarantee you to find it even if the first responder object is not a UIView.

    First lets implement a reversed version of it, using the next property of UIResponder:

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

    With this computed property, we can find the current first responder from bottom to top even if it's not UIView. For example, from a view to the UIViewController who's managing it, if the view controller is the first responder.

    However, we still need a top-down resolution, a single var to get the current first responder.

    First with the view hierarchy:

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

    This will search for the first responder backwards, and if it couldn't find it, it would tell its subviews to do the same thing (because its subview's next is not necessarily itself). With this we can find it from any view, including UIWindow.

    And finally, we can build this:

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

    So when you want to retrieve the first responder, you can call:

    let firstResponder = UIResponder.first
    
    0 讨论(0)
  • 2020-11-22 05:46

    Simplest way to find first responder:

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

    The default implementation dispatches the action method to the given target object or, if no target is specified, to the first responder.

    Next step:

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

    Usage: Just get UIResponder.actualFirst for your own purposes.

    0 讨论(0)
  • 2020-11-22 05:47

    If your ultimate aim is just to resign the first responder, this should work: [self.view endEditing:YES]

    0 讨论(0)
  • 2020-11-22 05:48

    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

    0 讨论(0)
提交回复
热议问题