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
For a Swift 3 & 4 version of nevyn's answer:
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
I have a slightly different approach than most. Rather than iterate through the collection of views looking for the one that has isFirstResponder
set, I too send a message to nil
, but I store the receiver of the message so I can return it and do whatever I wish with it.
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
}
@objc func storeFirstResponder(_ sender: AnyObject) {
// Capture the recipient of this message (self), which is the first responder
_foundFirstResponder = self
}
}
With the above, I can resign the first responder by simply doing this...
UIResponder.first?.resignFirstResponder()
But since my API actually hands back whatever the first responder is, I can do whatever I want with it.
Here's an example that checks if the current first responder is a UITextField
with a helpMessage
property set, and if so, shows it in a help bubble right next to the control. We call this from a 'Quick Help' button on our screen.
func showQuickHelp(){
if let textField = UIResponder?.first as? UITextField,
let helpMessage = textField.helpMessage {
textField.showHelpBubble(with:helpMessage)
}
}
The support for the above is defined in an extension on UITextField
like so...
extension UITextField {
var helpMessage:String? { ... }
func showHelpBubble(with message:String) { ... }
}
Now to support this feature, all we have to do is decide which text fields have help messages and the UI takes care of the rest for us.
Peter Steinberger just tweeted about the private notification UIWindowFirstResponderDidChangeNotification
, which you can observe if you want to watch the firstResponder change.
If you just need to kill the keyboard when the user taps on a background area why not add a gesture recognizer and use it to send the [[self view] endEditing:YES]
message?
you can add the Tap gesture recogniser in the xib or storyboard file and connect it to an action,
looks something like this then finished
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
[[self view] endEditing:YES];
}
Here is a Extension implemented in Swift based on Jakob Egger's most excellent answer:
import UIKit
extension UIResponder {
// Swift 1.2 finally supports static vars!. If you use 1.1 see:
// http://stackoverflow.com/a/24924535/385979
private weak static var _currentFirstResponder: UIResponder? = nil
public class func currentFirstResponder() -> UIResponder? {
UIResponder._currentFirstResponder = nil
UIApplication.sharedApplication().sendAction("findFirstResponder:", to: nil, from: nil, forEvent: nil)
return UIResponder._currentFirstResponder
}
internal func findFirstResponder(sender: AnyObject) {
UIResponder._currentFirstResponder = self
}
}
import UIKit
extension UIResponder {
private weak static var _currentFirstResponder: UIResponder? = nil
public static var current: UIResponder? {
UIResponder._currentFirstResponder = nil
UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
return UIResponder._currentFirstResponder
}
@objc internal func findFirstResponder(sender: AnyObject) {
UIResponder._currentFirstResponder = self
}
}
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:
UIResponder
so it can execute our own code on the first responder.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