I created an application and in ViewController.m , viewDidLoad , i wrote following code
for (UIView* v in self.view.subviews){
NSLog(#"View is %#",v);
NSLog(#"First Responder is %#",[v isFirstResponder]?#"YES":#"NO");
}
NSLog(#"First Responder is %#",[self isFirstResponder]?#"YES":#"NO");
NSLog(#"First Responder is %#",[self.view isFirstResponder]?#"YES":#"NO");
But it it returns NO for everything. What is my first responder by default ?
In your main view controller, do this:
for (UIView *sub in self.view.subviews)
{
if (sub.isFirstResponder)
NSLog(#"It's me!");
}
From documentation for OS X:
Determining First-Responder Status
Usually an NSResponder object can always determine if it's currently the first responder by asking its window (or itself, if it's an NSWindow object) for the first responder and then comparing itself to that object. You ask an NSWindow object for the first responder by sending it a firstResponder message. For an NSView object, this comparison would look like the following bit of code:
if ([[self window] firstResponder] == self) {
// do something based upon first-responder status
}
Note: This is for OS X. Unfortunately iOS doesn't have a similar system, but can be achieved via:
UIWindow* keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView* firstResponder = [keyWindow performSelector:#selector(firstResponder)];
This is undocumented, and could be rejected by Apple. For testing and exploratory research, this is handy for you.
Here are some documents on first responders in iOS:
Cocoa Application Competencies for iOS
Event Handling Guide for iOS
Related
I use to contextually switch key UIWindows in my app to provide a bit cleaner flow – Welcome window => Main screen with items list <=> Item container with burger menu and stuff.
Example function follows:
- (void)updateKeyWindow:(UIWindow *)window withTransition:(WindowTransition)transition
{
UIWindow *originalWindow = _keyWindow;
_keyWindow = window;
window.alpha = 0;
[originalWindow resignKeyWindow];
[originalWindow resignFirstResponder];
originalWindow.userInteractionEnabled = NO;
[window makeKeyAndVisible];
window.transform = (transition == WindowTransitionFlyDown) ? CGAffineTransformMakeScale(1.02, 1.02) :
(transition == WindowTransitionFlyUp) ? CGAffineTransformMakeScale(.96, .96) :
CGAffineTransformIdentity;
// [UIView animateWithDuration:.24 animations:^{
window.alpha = 1;
originalWindow.alpha = 0;
window.transform = CGAffineTransformIdentity;
originalWindow.transform = (transition == WindowTransitionFlyDown) ? CGAffineTransformMakeScale(.96, .96) :
(transition == WindowTransitionFlyUp) ? CGAffineTransformMakeScale(1.02, 1.02) :
CGAffineTransformIdentity;
// } completion:^(BOOL b){
[originalWindow resignFirstResponder];
[originalWindow removeFromSuperview];
[originalWindow.rootViewController.view removeFromSuperview];
originalWindow.rootViewController = nil;
originalWindow = nil;
// }];
}
I use animations to provide nice transitions, but I've commented it out to test if it's not the cause of the issue I have.
The thing is, after dropping originalWindow from the hierarchy and quitting the block/function, the UIWindow is NOT being released and hangs somewhere in the space. I've tested this with child class by putting breakpoint inside overloaded -dealloc.
I've checked both UIApplication's -keyWindow and AppDelegate's -window, both having new UIWindow object assigned.
However after tapping anywhere on the screen, the -dealloc for the previous UIWindow is triggered with some -[UITouch dealloc] stuff in the call stack.
I find this behaviour completely weird, there must be something wrong within the UIKit, I'm not expecting there's anything wrong on my side with this approach.
Refer to the resignKeyWindow documentation:
Never call this method directly. The system calls this method and posts UIWindowDidResignKeyNotification to let the window know when it is no longer key...
Try removing that and seeing if it fixes the crash.
The problem is UIApplication's keyWindow is weak by definition. So you should have to connect your UIWindow's strongly on some object; like ApplicationDelegate or some singleton.
In my iOS app I have several UIElements that can process user input: textfields, editable webviews, etc. each time I write something into these UIElements the keyboard (obviously) will come up. Before it happens I can catch this event by observing the UIKeyboardWillShowNotification.
I would like to know what's the way to find out which UIElement invoked this action.
Thanks for your help!
The keyboard is launched when the view tapped by a user is set as FirstResponder, so I think this question is the equivalent of saying how do I get the current first responder when UIKeyboardWillShowNotification is received?.
The answer to that question by Thomas Muller was to use a class extension along the lines of:
#implementation UIView (FindFirstResponder)
- (UIView *)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.subviews) {
UIView *firstResponder = [subView findFirstResponder];
if (firstResponder != nil) {
return firstResponder;
}
}
return nil;
}
#end
So I think you could use that inside your handler for UIKeyboardWillShow to figure out what caused it.
I think the section 4 (Moving Content That Is Located Under the Keyboard) of this document can give you a hint about knowing wich element has the keyboard.
http://developer.apple.com/library/ios/#DOCUMENTATION/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html#//apple_ref/doc/uid/TP40009542-CH5-SW1
I am new to Objective-C, and I am looking to limit a user from switching from the alphabet portion of a normal keyboard to the numeric/punctuation side. This being said, I would like to disable the button on the keyboard that does the switch. Maybe I'm putting in the wrong search parameters, but I'm finding little on Google and even less in my reference books. How would I go about actually disabling a button on an iOS keyboard? With alpha/numeric/punctuation characters this would be easily done by just ignoring the inputed characters that were entered, but the button that switches between keyboard portions does an action as opposed to returning a character.
Thank you for your help!
You actually can add and remove buttons of the default UIKeyboard
There's some recipes on the Internet like this: http://www.iphonedevsdk.com/forum/iphone-sdk-development/6573-howto-customize-uikeyboard.html and like this: http://www.iphonedevsdk.com/forum/iphone-sdk-development/6275-add-toolbar-top-keyboard.html
Those posts show you how to add a button, however the same principle can be used to remove.
Below I'll show you a compilation of one of the solutions:
//The UIWindow that contains the keyboard view -
//It some situations it will be better to actually
//iterate through each window to figure out where the keyboard is,
// but In my applications case
//I know that the second window has the keyboard so I just reference it directly
//
UIWindow* tempWindow = [[[UIApplication sharedApplication] windows]
// objectAtIndex:1];
//Because we cant get access to the UIKeyboard throught the SDK we will
// just use UIView.
//UIKeyboard is a subclass of UIView anyways
UIView* keyboard;
//Iterate though each view inside of the selected Window
for(int i = 0; i < [tempWindow.subviews count]; i++)
{
//Get a reference of the current view
keyboard = [tempWindow.subviews objectAtIndex:i];
//Check to see if the className of the view we have
//referenced is "UIKeyboard" if so then we found
//the keyboard view that we were looking for
if([[keyboard description] hasPrefix:#"<UIKeyboard"] == YES)
{
// Keyboard is now a UIView reference to the
// UIKeyboard we want. From here we can add a subview
// to th keyboard like a new button
//Do what ever you want to do to your keyboard here...
}
}
I implemented a neater solution. The trick is to place a disabled key image on top of the keyboard.
To do this
Run emulator (in 100% scale) and screen grab the asset you'd like (in my case, this was a disabled Done button at the bottom right end)
Place this image on top of the keyboard
Note that keyboard is placed in a separate UIWindow (since iOS5 I believe) and thus, you will need to do the following
+ (void) addSubviewToTop:(UIView *)view {
int count = [[[UIApplication sharedApplication] windows] count];
if(count <= 0) {
warn(#"addSubviewToTop failed to access application windows");
}
UIWindow *top_window = [[[UIApplication sharedApplication] windows] objectAtIndex:count-1];
[top_window addSubview:view];
[top_window bringSubviewToFront:view];
}
I'm not sure if the SDK has changed such that #ppaulojr's answer no longer works, or if I just have things set up weirdly on my system, but with the following tweaks I was able to get it to work!
The posts linked in #ppaulojr's answer are great (http://www.iphonedevsdk.com/forum/iphone-sdk-development/6573-howto-customize-uikeyboard.html and http://www.iphonedevsdk.com/forum/iphone-sdk-development/6275-add-toolbar-top-keyboard.html), and they helped me to get this to work.
Apparently the actual keyboard view is now embedded as a subview in some grander UIKeyboard view structure so a bit of recursion is involved. I got this to work:
-(void) findKeyboard {
NSArray* windows = [[UIApplication sharedApplication] windows];
for (int i = 0; i < [windows count]; i++) {
UIWindow* tempWindow = [[[UIApplication sharedApplication] windows]
objectAtIndex:i];
for(UIView *subView in [tempWindow subviews])
{
[self checkViews:subView];
}
}
}
-(void)checkViews:(UIView *)inView
{
for(UIView *keyboard in inView.subviews)
{
NSLog( #"ViewName: %#", [keyboard description] ); // Which view are we looking at
//Check to see if the className of the view we have
//referenced is "UIKeyboard" if so then we found
//the keyboard view that we were looking for
if([[keyboard description] hasPrefix:#"<UIKeyboard"] == YES)
{
// Keyboard is now a UIView reference to the
// UIKeyboard we want. From here we can add a subview
// to th keyboard like a new button
//Do what ever you want to do to your keyboard here...
break;
}
// Recurse if not found
[self checkViews:subView];
}
}
I also found that the best place to call this function is from -(void)textViewDidBeginEditing:(UITextView *)textView like so:
- (void)textViewDidBeginEditing:(UITextView *)textView {
NSLog(#"textViewDidBeginEditing");
[self findKeyboard];
}
This does the keyboard modifications as soon as the keyboard is added to the window, but before it actually shows up, so that the whole time it raises from the bottom, it will have been modified.
I need to keep track of which text field is the firstResponder for my custom keyboard to work. In the code below, I have grossly oversimplified my program, but here is the gist of the problem:
#implementation SimplePickerViewController
#synthesize pickerKeyboard;
#synthesize textView;
#synthesize textView2;
#synthesize firstResponder;
-(void)viewDidLoad{
pickerKeyboard = [[PickerKeyboardViewController alloc] initWithNibName:#"PickerKeyboard" bundle:nil];
pickerKeyboard.delegate = self;
[self.textView setInputView:pickerKeyboard.view];
[self.textView setDelegate:self];
[self.textView2 setInputView:pickerKeyboard.view];
[self.textView2 setDelegate:self];
}
-(void)hideKeyboard{
[self.firstResponder resignFirstResponder];
self.firstResponder = nil; //without this line, the code doesn't work.
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
self.firstResponder = textView;
[self.pickerKeyboard.picker reloadAllComponents];
return YES;
}
If I remove the line setting the firstResponder to nil, the code ceases to function properly, but I am not sure why. (Without that line, I can select the first textView to bring up the keyboard, but after that I can never bring the keyboard back. Any ideas? Thanks!
I'm not sure that I understand why firstResponder needs to be kept track of for a custom keyboard to work. I use a custom keyboard without knowing what the first responder is.
Do you use:
textView.inputView = pickerKeyboard
How about the following, called on the view to resign the first responder:
[self.view endEditing:NO];
I have had a similar problem and I have just figured out the issue. Somewhere in some part of Apple's first responder code, they are using a selector named firstResponder. When you created the property firstResponder you inadvertently overrode that selector. That will cause Apple's code to fail. This, in my humble opinion, is a bug in Apple's framework, and the firstResponder method isn't documented anywhere. Name your property myFirstResponder or anything else and everything should work just fine.
See Why does the keyboard not show when a view is popped from the navigation stack?
I am writing a SplitView iPad app. Inside the DetailViewController, there's a little view that contains a UITableView and a UISearchBar and its controller. This view does not represent the whole screen space reserved for the DetailViewController. Actually, it uses just half of it. There's an UIImageView on the other half.
And this is where trouble comes in: every time I use the search bar, the displaycontroller (I assume) dims everything present inside the DetailViewController, including the image view. That is not consistent with what someone would expect when running the app. Is there any way to set the frame to be dimmed? Or at least disable dimming for good?
Thanks in advance.
You are correct that it is the UISearchDisplayController that is managing the "dimming" effect that you're seeing.
What the UISearchDisplayController is doing is adding a UIControl as a subview to the view of the searchContentsController (a property of UISearchDisplayController), which is likely your detail-view controller. This UIControl is just an alpha'd view with a gray background. It seems to have a touch-up-inside event handler that ends searching when tapped.
To constrain the dimming effect to your sub-view of the detail-view, you need to do three things. (I'm assuming your detail-view-controller is defined via a xib. If not, these steps can be done in code too.)
1) add a new UIViewController to your detail-view-controller xib. Attach this new view-controller to an IBOutlet of your detail-view-controller. In my example I call this "_searchAreaViewController". This is important, even if you wont ever access the view controller (but remember, you'll have to release it at some point)
#interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate, UITableViewDelegate, UITableViewDataSource> {
UIPopoverController *popoverController;
UIToolbar *toolbar;
id detailItem;
UILabel *detailDescriptionLabel;
IBOutlet UIViewController* _searchAreaViewController;
}
2) make the containing view for your search area the view of this new view-controller. To do this, use Interface Builder to set a new referencing outlet for this view by dragging the outlet to the searchAreaViewController and selecting the "view" outlet. You must have a containing view - it should be a subview of your detail-view, and it should contain the UISearchBar and likely your UITableView.
3) make the searchContentsController property of the UISearchDisplayController refer to this new view controller instead of the detail-view-controller. This can only be done via Interface Builder as the property is read-only (IB has some magic to make this work?) If you need to do this step via code you'll have to subclass the UISearchDisplayController and return the correct value from a property override of "searchContentsController".
I made a sample app to demonstrate this and the only line of code I had to add to the SplitView template was the one listed in step 1 above. Everything else was just adding the views/controllers and connecting them properly in IB.
good luck!
iOS 8+
[[UIView appearanceWhenContainedInInstancesOfClasses:#[NSClassFromString(#"UISearchDisplayControllerContainerView")]] setHidden:YES];
iOS 7
[View appearanceWhenContainedIn:NSClassFromString(#"UISearchDisplayControllerContainerView"), nil] setHidden:YES];
I know, that UISearchDisplayController is deprecated for now, but if you still need to use it, you can solve your issue with one line of code perfectly. Add it to viewDidLoad method.
Could you clarify what you mean by "use the search bar" and "dims everything present"? I interpret what you wrote in such a way that the keyboard pops up when you are about to enter text in the text field of the search bar. And that at this point the detail view is dimmed out, preventing user interaction.
The cause is that the search bar implements a modal dialog which prevents user interaction with the view as long as the keyboard is shown. Unfortunately, there doesn't seem to be any way to configure the search bar to prevent this behavior. On the other hand I am not sure that the user won't expect this behavior since search bars are modal consistently and behave like this in general under iOS.
I have tried two work-arounds:
1.) There is a property of the UIViewController called modalPresentationStyle which produces exactly the behavior you describe if it has the value UIModalPresentationFormSheet ("All uncovered areas are dimmed to prevent the user from interacting with them.", see the Apple documentation). But setting this property to a different values does not change the result (at least for me it didn't work).
2.) You would need to write your own non-modal search bar replacement since a standard UITextField is non-modal and thus does not dim out any other UI elements. This approach works, but you might need a little more work to make it look like a "regular" search bar. But, again, since this search bar behaves differently from the modal normal search bars in iOS this might not really be what the users expect.
I know I am late and this is a horrible idea here, but 'setHidden:No' did not work for me.
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
BOOL hasBeenremoved = NO;
hasBeenremoved = [[[[NSThread mainThread] threadDictionary] objectForKey:#"hasBeenremoved"] boolValue];
if (hasBeenremoved)
{
UIView* dimmingView = nil;
dimmingView = [[[NSThread mainThread] threadDictionary] objectForKey:#"dimmingView"];
UIView* dimmingViewSuperView = nil;
dimmingViewSuperView = [[[NSThread mainThread] threadDictionary] objectForKey:#"dimmingViewSuperView"];
[dimmingViewSuperView addSubview:dimmingView];
[[[NSThread mainThread] threadDictionary] setObject:#NO forKey:#"hasBeenremoved"];
}
if ([searchText length] == 0 || [searchText isEqualToString:#""] )
{
[searchBar becomeFirstResponder];
[[[self primarySearchDisplayController] searchResultsTableView] reloadData];
[[[self primarySearchDisplayController] searchResultsTableView] setHidden:NO];
for( UIView *subview in self.view.subviews )
{
if([subview isMemberOfClass:[UIControl class]] ||
([[[subview class] description] isEqualToString:#"UISearchDisplayControllerContainerView"]))
{
for(UIView *subView2 in subview.subviews)
{
for(UIView *subView3 in subView2.subviews)
{
if (subView3.alpha < 1)
{
if ([[[subView3 class] description] isEqualToString:#"_UISearchDisplayControllerDimmingView"])
{
[[[NSThread mainThread] threadDictionary] setObject:subView3 forKey:#"dimmingView"];
[[[NSThread mainThread] threadDictionary] setObject:subView3.superview forKey:#"dimmingViewSuperView"];
[[[NSThread mainThread] threadDictionary] setObject:#YES forKey:#"hasBeenremoved"];
[subView3 removeFromSuperview];
}
}
}
}
}
}
}
}