Conversion of custom init routines in Swift - objective-c

I need to subclass SVModalWebViewController.m (GitHub), which is itself a subclass of UINavigationController, and am incorporating this code into a new app that will be created entirely in Swift. I've run into several issues, so I decided to first convert just this very simple class into Swift to debug.
Initialization is a little different in Swift compared with Objective-C (read more about Swift initialization here), which is why I'm not returning self in the Swift code.
Here's the original Obj-C code (minus the *.h, which instantiates barsTintColor):
#import "SVModalWebViewController.h"
#import "SVWebViewController.h"
#interface SVModalWebViewController ()
#property (nonatomic, strong) SVWebViewController *webViewController;
#end
#implementation SVModalWebViewController
#pragma mark - Initialization
- (id)initWithAddress:(NSString*)urlString {
return [self initWithURL:[NSURL URLWithString:urlString]];
}
- (id)initWithURL:(NSURL *)URL {
self.webViewController = [[SVWebViewController alloc] initWithURL:URL];
if (self = [super initWithRootViewController:self.webViewController]) {
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self.webViewController
action:#selector(doneButtonClicked:)];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
self.webViewController.navigationItem.leftBarButtonItem = doneButton;
else
self.webViewController.navigationItem.rightBarButtonItem = doneButton;
}
return self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:NO];
self.webViewController.title = self.title;
self.navigationBar.tintColor = self.barsTintColor;
}
#end
And here's the entire Swift version of the class (I simplified to just one init method since I didn't need to init with URL specifically):
import UIKit
class SVModalWebViewController: UINavigationController {
var webViewController: SVWebViewController!
var barsTintColor: UIColor?
// This was added because of a compiler error indicating it needed to be added
init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
init(address urlString: String!) {
let url: NSURL = NSURL.URLWithString(urlString)
self.webViewController = SVWebViewController(URL: url)
super.init(rootViewController: self.webViewController)
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self.webViewController, action: Selector("doneButtonClicked:"))
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
self.navigationItem.leftBarButtonItem = doneButton
} else {
self.navigationItem.rightBarButtonItem = doneButton
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// self.title may not be set, and is considered an optional in Swift, so we have to check first
if self.title {self.webViewController.title = self.title}
self.navigationBar.tintColor = self.barsTintColor
}
}
The issue I'm having relates to setting the Done button in the navigationItem. Currently they're not showing up at all when this code is called:
let webViewController = SVModalWebViewController(address: "http://www.google.com");
webViewController.barsTintColor = UIColor.blueColor()
self.presentModalViewController(webViewController, animated: true)
The modal view appears just fine and I'm able to correctly set the bar color property, but the Done button does not show up. It appears that I don't have proper access to the navigationItem of the UINavigationController. Note that I had to add the init with nibName method due to a compiler error without it. This wasn't required in the Obj-C code and I'll admit I'm not sure why it's needed in Swift - that could be part of the issue.
Any thoughts as to why I cannot set the self.navigationItem.leftBarButtonItem property of the UINavigationController? Thanks!

A UINavigationController does not have a navigationItem. Well, it does, but it's useless. It is the navigationItem of the child controller (in this case, the rootViewController, which you are also calling self.webViewController) that appears in the navigation bar.

Related

delegate from a objc to swift to dismiss modal view

I have a objc uiviewcontroller that perform a segue to a swift uiviewcontroller in modal. I create the delegate in the objc file, but i can't call the method in objc from the swift file. All the files seams to be ok, but when calling delegate.function gives me a bad exec in the swift file.
objc
#import "Cliente-Swift.h"
#interface ViewController () <gameDelegate>
#end
(...)
else if ([[segue identifier] isEqualToString:#"segueToGame"]) {
//jump to main vgame controller
GameViewController *gameVC = [segue destinationViewController];
gameVC.mydelegate = self;
}
swift GameViewController
#objc protocol gameDelegate {
func dismissGame()
}
class GameViewController: UIViewController {
var mydelegate: gameDelegate! = nil
override func viewDidLoad() {
super.viewDidLoad()
let scene = MainMenuScene(size:CGSize(width: 1536, height: 2048))
//let scene = GameScene(size:CGSize(width: 1536, height: 2048))
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.showsPhysics = true;
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
override func prefersStatusBarHidden() -> Bool {
return true
}
func sairDoJogo() {
print("mydelegate = \(mydelegate!)")
mydelegate!.dismissGame()
the bad exec is where in the call to the objc function dismissGame
I would suspect, that mydelegate is not really set.
You should check whether mydelegate is really set in prepareForSegue:... maybe by adding a log output: (Is your segue id correct?)
else if ([[segue identifier] isEqualToString:#"segueToGame"]) {
//jump to main vgame controller
NSLog(#"Setting game delegate");
GameViewController *gameVC = [segue destinationViewController];
gameVC.mydelegate = self;
}
In GameViewController you may either check whether or not mydelegate is nil before the call to dismissGame(), or just use ?: (As you declare mydelegate as gameDelegate!, the ! in your call is not necessary, as it is "implicitly there" - but you can still use mydelegate?.dismissGame())
if mydelegate != nil {
mydelegate.dismissGame()
}
or
mydelegate?.dismissGame()
(I made a quick test with a sample project and having mydelegate not initialized leads to the errors you described.)
thanks for the answers. I got it. Theres nothing about delegation, or objc and swift integration. The delegate are fine and all the rest too. The problem is how i'm calling back the uiviewcontroller from the spritekit scene, as soon as i realize that I solve the problem. Thanks again

textFieldShouldReturn called without the UITextFieldDelegate

I have a FooViewController that loads a FooView and then adds a TextField :
// FooViewController.m
#interface FooViewController () <UITextFieldDelegate>
#end
...
-(void)loadView {
CGRect aFrame = CGRectMake(160, 240, 100, 150);
FooView *fv = [[FooView alloc] initWithFrame:aFrame];
...
// TextField
CGRect tfRect = CGRectMake(50, 50, 150, 50);
UITextField *aTextField = [[UITextField alloc] initWithFrame:tfRect];
aTextField.borderStyle = UITextBorderStyleRoundedRect;
aTextField.returnKeyType = UIReturnKeyDone;
aTextField.delegate = self;
[fv addSubview:aTextField];
self.view = fv;
}
In this class implementation (FooViewController.m), I have the following method :
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
return YES;
}
The whole code works well.
Just out of curiosity, I removed the <UITextFieldDelegate> mention and the textFieldShouldReturn is still get called.
How is that possible ?
That's okay,
There are 2 ways to implement safe delegating
1)
if ([self.delegate respondsToSelector:#selector(textFieldShouldReturn)]) {
[self.delegate textFieldShouldReturn];
}
2)
if ([self.delegate respondsToProtocol:#protocol(UITextFieldDelegate)]) {
[self.delegate textFieldShouldReturn];
}
Seems Apple uses first one, so your instance responds to textFieldShouldReturn and the call is made.
Objective-C is not as type-safe as other languages and does not care whether the delegate explicitly declares conformance using <UITextFieldDelegate>. The required delegate methods will be always called and if they are not implemented an exception will be thrown.
If your controller class (and its superclasses) do not declare <UITextFieldDelegate> then the following code should cause a compiler warning:
// warning that controller does not declare <UITextFieldDelegate>
textField.delegate = controller;
Note that with Swift this is no longer possible and will result in a compiler error instead.

How do I get NSTextFinder to show up

I have a mac cocoa app with a webview that contains some text. I would like to search through that text using the default find bar provided by NSTextFinder. As easy as this may seem reading through the NSTextFinder class reference, I cannot get the find bar to show up. What am I missing?
As a sidenote:
- Yes, I tried setting findBarContainer to a different view, same thing. I reverted back to the scroll view to eliminate complexity in debugging
- performTextFinderAction is called to perform the find operation
**App Delegate:**
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.textFinderController = [[NSTextFinder alloc] init];
self.webView = [[STEWebView alloc] initWithFrame:CGRectMake(0, 0, self.window.frame.size.width, 200)];
[[self.window contentView] addSubview:self.webView];
[self.textFinderController setClient:self.webView];
[self.textFinderController setFindBarContainer:self.webView.enclosingScrollView];
[[self.webView mainFrame] loadHTMLString:#"sample string" baseURL:NULL];
}
- (IBAction)performTextFinderAction:(id)sender {
[self.textFinderController performAction:[sender tag]];
}
**STEWebView**
#interface STEWebView : WebView <NSTextFinderClient>
#end
#implementation STEWebView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
- (NSUInteger) stringLength {
return [[self stringByEvaluatingJavaScriptFromString:#"document.documentElement.textContent"] length];
}
- (NSString *)string {
return [self stringByEvaluatingJavaScriptFromString:#"document.documentElement.textContent"];
}
In my tests, WebView.enclosingScrollView was null.
// [self.textFinderController setFindBarContainer:self.webView.enclosingScrollView];
NSLog(#"%#", self.webView.enclosingScrollView);
Using the following category on NSView, it is possible to find the nested subview that extends NSScrollView, and set that as the container, allowing the NSTextFinder to display beautifully within a WebView
#interface NSView (ScrollView)
- (NSScrollView *) scrollView;
#end
#implementation NSView (ScrollView)
- (NSScrollView *) scrollView {
if ([self isKindOfClass:[NSScrollView class]]) {
return (NSScrollView *)self;
}
if ([self.subviews count] == 0) {
return nil;
}
for (NSView *subview in self.subviews) {
NSView *scrollView = [subview scrollView];
if (scrollView != nil) {
return (NSScrollView *)scrollView;
}
}
return nil;
}
#end
And in your applicationDidFinishLaunching:aNotification:
[self.textFinderController setFindBarContainer:[self scrollView]];
To get the Find Bar to appear (as opposed to the default Find Panel), you simply have to use the setUsesFindBar: method.
In your case, you'll want to do (in your applicationDidFinishLaunching:aNotification method):
[textFinderController setUsesFindBar:YES];
//Optionally, incremental searching is a nice feature
[textFinderController setIncrementalSearchingEnabled:YES];
Finally got this to show up.
First set your NSTextFinder instances' client to a class implementing the <NSTextFinderClient> protocol:
self.textFinder.client = self.textFinderController;
Next, make sure your NSTextFinder has a findBarContainer set to the webView category described by Michael Robinson, or get the scrollview within the webView yourself:
self.textFinder.findBarContainer = [self.webView scrollView];
Set the find bar position above the content (or wherever you wish):
[self.webView scrollView].findBarPosition = NSScrollViewFindBarPositionAboveContent;
Finally, tell it to show up:
[self.textFinder performAction:NSTextFinderActionShowFindInterface];
It should show up in your webView:
Also, not sure if it makes a difference, but I have the NSTextFinder in the XIB, with a referencing outlet:
#property (strong) IBOutlet NSTextFinder *textFinder;
You may also be able to get it by simply initing it like normal: self.textFinder = [[NSTextFinder alloc] init];

How to add associated object to uitapgesture?

Currently I have in my code
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(addCompoundToLabel:)];
My code would be cleaner if it was something like
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(addCompoundToLabel:) withObject:data];
I read that associated objects allow you to pass objects in a gesture recognizer, but after reading the docs, I don't quite understand how to implement it into my code. An example implementation of associated objects would be appreciated. Thanks
EDIT:
this is what the beginning of addCompound looks like
- (void)addCompoundToLabel:(UIGestureRecognizer *)recognizer {
if( [recognizer state] == UIGestureRecognizerStateEnded ) {
FormulaLabel* label = (FormulaLabel*)[recognizer view];
Using associated objects is a messy solution. Instead, you could create a false target object, give it your UIView and your Data, and have access to both of them in the selector method. It is a lot more readable, and your code expresses your intent a lot better.
Here is a quick and dirty example:
#interface FalseTarget : NSObject {
MyViewController *_viewCtrl;
MyData *_data;
}
-(id)initWithViewCtrl:(MyViewController*)viewCtrl andData:(MyData*)data;
-(void)tap:(id)sender;
#end
#implementation
-(id)initWithViewCtrl:(MyViewController*)viewCtrl andData:(MyData*)data {
self = [super init];
if (self) {
_viewCtrl = viewCtrl;
_data = data;
}
return self;
}
-(void)tap:(id)sender {
[_viewCtrl processTapFromSender:sender withData:_data];
}
#end
Add processTapFromSender:withData: method to your controller:
-(void)processTapFromSender:(id)sender withData:(MyData*)data {
// Your action
}
Now you can create your tap recognizer like this:
FalseTarget *target = [[FalseTarget alloc] initWithViewCtrl:self andData:data];
tap = [[UITapGestureRecognizer alloc] initWithTarget:target action:#selector(tap:)];

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.
If your ultimate aim is just to resign the first responder, this should work: [self.view endEditing:YES]
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`
}
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)
Here's a category that allows you to quickly find the first responder by calling [UIResponder currentFirstResponder]. Just add the following two files to your project:
UIResponder+FirstResponder.h:
#import <Cocoa/Cocoa.h>
#interface UIResponder (FirstResponder)
+(id)currentFirstResponder;
#end
UIResponder+FirstResponder.m:
#import "UIResponder+FirstResponder.h"
static __weak id currentFirstResponder;
#implementation UIResponder (FirstResponder)
+(id)currentFirstResponder {
currentFirstResponder = nil;
[[UIApplication sharedApplication] sendAction:#selector(findFirstResponder:) to:nil from:nil forEvent:nil];
return currentFirstResponder;
}
-(void)findFirstResponder:(id)sender {
currentFirstResponder = self;
}
#end
The trick here is that sending an action to nil sends it to the first responder.
(I originally published this answer here: https://stackoverflow.com/a/14135456/322427)
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
}
}
Swift 4
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
}
}
It's not pretty, but the way I resign the firstResponder when I don't know what that the responder is:
Create an UITextField, either in IB or programmatically. Make it Hidden. Link it up to your code if you made it in IB.
Then, when you want to dismiss the keyboard, you switch the responder to the invisible text field, and immediately resign it:
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
For a Swift 3 & 4 version of nevyn's answer:
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
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
Using Swift and with a specific UIView object this might help:
func findFirstResponder(inView view: UIView) -> UIView? {
for subView in view.subviews as! [UIView] {
if subView.isFirstResponder() {
return subView
}
if let recursiveSubView = self.findFirstResponder(inView: subView) {
return recursiveSubView
}
}
return nil
}
Just place it in your UIViewController and use it like this:
let firstResponder = self.findFirstResponder(inView: self.view)
Take note that the result is an Optional value so it will be nil in case no firstResponder was found in the given views subview hierarchy.
The first responder can be any instance of the class UIResponder, so there are other classes that might be the first responder despite the UIViews. For example UIViewController might also be the first responder.
In this gist you will find a recursive way to get the first responder by looping through the hierarchy of controllers starting from the rootViewController of the application's windows.
You can retrieve then the first responder by doing
- (void)foo
{
// Get the first responder
id firstResponder = [UIResponder firstResponder];
// Do whatever you want
[firstResponder resignFirstResponder];
}
However, if the first responder is not a subclass of UIView or UIViewController, this approach will fail.
To fix this problem we can do a different approach by creating a category on UIResponder and perform some magic swizzeling to be able to build an array of all living instances of this class. Then, to get the first responder we can simple iterate and ask each object if -isFirstResponder.
This approach can be found implemented in this other gist.
Hope it helps.
Iterate over the views that could be the first responder and use - (BOOL)isFirstResponder to determine if they currently are.
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.
Additionally, I zero out the optional that holds the found responder in a defer statement from within the call itself. This ensures no references remain--even weak ones--at the end of the call.
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
}
// Make sure to mark this with '#objc' since it has to be reachable as a selector for `sendAction`
#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];
}
Just it case here is Swift version of awesome Jakob Egger's approach:
import UIKit
private weak var currentFirstResponder: UIResponder?
extension UIResponder {
static func firstResponder() -> UIResponder? {
currentFirstResponder = nil
UIApplication.sharedApplication().sendAction(#selector(self.findFirstResponder(_:)), to: nil, from: nil, forEvent: nil)
return currentFirstResponder
}
func findFirstResponder(sender: AnyObject) {
currentFirstResponder = self
}
}
This is what I did to find what UITextField is the firstResponder when the user clicks Save/Cancel in a ModalViewController:
NSArray *subviews = [self.tableView subviews];
for (id cell in 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]) {
[theTextField resignFirstResponder];
}
}
}
}
}
This is what I have in my UIViewController Category. Useful for many things, including getting first responder. Blocks are great!
- (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;
}];
}
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?
You can choose the following UIView extension to get it (credit by Daniel):
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
return subviews.first(where: {$0.firstResponder != nil })
}
}
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
This is good candidate for recursion! No need to add a category to UIView.
Usage (from your view controller):
UIView *firstResponder = [self findFirstResponder:[self view]];
Code:
// This is a recursive function
- (UIView *)findFirstResponder:(UIView *)view {
if ([view isFirstResponder]) return view; // Base case
for (UIView *subView in [view subviews]) {
if ([self findFirstResponder:subView]) return subView; // Recursion
}
return nil;
}
you can call privite api like this ,apple ignore:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(#"firstResponder");
UIView *firstResponder = [keyWindow performSelector:sel];
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
}
}
I would like to shared with you my implementation for find first responder in anywhere of UIView. I hope it helps and sorry for my english. Thanks
+ (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;
}
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]);
}
}
}
}
}
}
}
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
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;
}
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.