I'm trying to give my users a way to dismiss the keyboard, whether by clicking outside the keyboard or by having a DONE button on the keyboard itself.
I have created a Done button and it works fine on iOS 6:
UIToolbar *keyboardToolbar;
keyboardToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, self.view.bounds.size.height - 44, 320, 44)];
UIBarButtonItem *flexItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *doneItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(#"dismiss_keyboard", nil) style:UIBarButtonItemStyleDone target:self action:#selector(dismissKeyboard)];
NSArray *items = [NSArray arrayWithObjects:flexItem,doneItem, nil];
[keyboardToolbar setItems:items animated:YES];
for (UIView *subview in [searchBar subviews])
{
if( [subview isKindOfClass:[UITextField class]] )
{
((UITextField*)subview).delegate=self;
((UITextField*)subview).inputAccessoryView = keyboardToolbar;
break;
}
}
But on iOS 7 this button is no where to be found.
I also tried using the method where the user can click anywhere but the keyboard and make it disappear:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
//Code to dismiss keyboard.
}
But my view contains a UISearchBar and a UITableView but the touchesBegan event doesn't fire when I touch those, only when I touch the parent UIView, which is not visible because it's covered by my UISearchBar and my UITableView. I have to touch the tiny space in between the two to fire the event.
How can I make my touchesBegan method apply to any object on the screen? And why is my DONE button not showing up in iOS 7?
Why is my DONE button not showing up in iOS 7?
Your DONE button isn't showing up because the internal structure of UISearchBar, which you aren't supposed to modify, has changed. (This is the reason you're not supposed to modify it.)
If you want to continue this non-recommended behavior and get it working, instead of checking if it's a UITextField, you can try checking if it conforms to UITextInputTraits, and iterate through subviews of subviews:
for(UIView *subView in [searchBar subviews]) {
if([subView conformsToProtocol:#protocol(UITextInputTraits)]) {
// iOS 6
[(UITextField *)subView setReturnKeyType: UIReturnKeyDone];
} else {
// iOS 7
for(UIView *subSubView in [subView subviews]) {
if([subSubView conformsToProtocol:#protocol(UITextInputTraits)]) {
[(UITextField *)subSubView setReturnKeyType: UIReturnKeyDone];
}
}
}
(This code is from this SO answer.)
However, this approach is not recommended, because it may break again in iOS 7.1. It also could be more resilient as a recursive method.
How can I make my touchesBegan method apply to any object on the screen?
Touch events are handled by the top view, so the UIView will only get them if the other views don't want them. The easiest approach here is to make an invisible UIButton that covers your whole screen, and if it's tapped, dismiss the keyboard and remove the button.
Use the below approach & get that Done button on your keyboard in iOS7.
Sample code is here.
Screenshot of Done button after using this approach is here.
Related
I am trying to remove the keyboard when it is in editing mode of a textview.I have added tap gesture on the main view.But on the click of when editing mode is for textfield then keyboard is removed but when editing mode is for textview then keyboard is not removed.Please tell me how can i tackle this issue?
added tap gesture to the main view.
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
[self.main_view setUserInteractionEnabled:true];
[self.main_view addGestureRecognizer:singleFingerTap];
calling method
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer
{
if([self.txt_username isFirstResponder])
{
[self.txt_username resignFirstResponder];
}
if([self.txt_password isFirstResponder])
{
[self.txt_password resignFirstResponder];
}
}
I have already set the delegate for text view & also i have added the textview protocol.
delete code for set setUserInteractionEnabled =true , u not need that
and add this in your func
-(void)handleSingleTap:(UITapGestureRecognizer *)recognizer
{
[self.view endEditing:YES];
}
or u can use library TPKeyboardAvoiding , set for scrollview..its automatic close keyboard when u tap view..so u not need that UITapGestureRecognizer
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[[self view] endEditing:YES];
}
When there are new or changed touches for a given phase, the app object calls one of these methods. Each method takes two parameters: a set of touches and an event.
For more info Click here
i think you just put only textview resingFirstresponder.
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer
{
[self.txt_username resignFirstResponder];
}
use it.
I want a pretty simple thing - in my top controller (which is navigation controller) to set up a tap gesture recognizer which will catch all the taps everywhere on the view. Currently when I tap on a button the system is not even thinking to bother my recognizer (except the gestureRecognizer:shouldReceiveTouch: delegate method, where I return YES). Instead, it just executes a button click. So I want to install "the strongest" recognizer on a view hierarchy no matter what.
You might try putting an empty UIView on top of all other views and add the UITapGestureRecognizer to it. We do something similar with help overlays. The biggest issue is figuring out how and when to ignore the touches so the underlying buttons get them when needed.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *b = [UIButton buttonWithType:UIButtonTypeInfoDark];
b.frame = CGRectMake(50,50, b.bounds.size.width, b.bounds.size.height );
[self.view addSubview:b];
UIView *invisibleView = [[UIView alloc] initWithFrame:self.view.bounds];
invisibleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[invisibleView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapHit:)]];
[self.view addSubview:invisibleView];
}
-(void)tapHit:(UITapGestureRecognizer*)tap {
NSLog( #"tapHit" );
}
#end
I am adding the UISwitch programmatically in a scroll view.
UIVIew -> UIScrollView -> UISwitch
UISwitch *toggleSwitch = [[UISwitch alloc] initWithFrame: CGRectZero];
[toggleSwitch addTarget:self action:#selector(flipMode:) forControlEvents:UIControlEventValueChanged];
toggleSwitch.on = YES;
toggleSwitch.userInteractionEnabled = YES;
[scrollView addSubview: toggleSwitch];
Action method:
- (IBAction)flipMode:(id)sender{
if([sender isOn])
{
// On Toggle ON
} else {
//On Toggle OFF
}
}
When we toggle the switch multiple times or on dragging or moving the switch slowly from ON to OFF state or vice versa at some point of time the action is not getting called. In next moment on value change it will trigger the event.
Occurrence of this issue is 2 out of 15-20 trials. Unable to find the root cause for it.
Help appreciated
set your delaysContentTouches property of scroll view to NO
yourScrollView.delaysContentTouches = NO;
This will cause your switch to "get" the touches immediately, rather
than have them go to the UIScrollView first.
Check this answer.
your flipMethod: should look like below code
-(void)flipMode:(id)sender
{
if([sender isOn])
[toggleSwitch setOn:YES animated:YES];
else
[toggleSwitch setOn:NO animated:YES];
}
I have a button, it works as usual on iOS 5 and 6. But on iOS 7 when I press the button the keyboard dismisses, but the method is not called. When I press it second time it works as intended.
Why is that?
Here is the code:
[self.loginButton addTarget:self action:#selector(loginButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
I have the button in UITableView cell.
EDIT:
Here is how I dismiss keyboard at the beginning of this method, but this method is not getting called o iOS7 until the second time I tap on the button.
for (UITextField *field in #[self.loginField, self.passwordField]) {
if ([field isFirstResponder]) {
[field resignFirstResponder];
}
}
I also have gesture recognizer to remove keyboard on tap outside:
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(removeKeyboard)];
[self.view addGestureRecognizer:tapper];
tapper.cancelsTouchesInView = NO;
Here is its method:
- (void) removeKeyboard
{
[self traverseAllSubviewsOfView:self.view withBlock:^(UIView *inView) {
[inView resignFirstResponder];
}];
}
Here is what helped me - I set controller as delegate for gesture recognizer and implement following method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// test if touch is on button
if ([touch.view isKindOfClass:[UIControl class]]) {
return NO;
}
return YES; // handle the touch
}
Did you check the cancelsTouchesInView property of the gesture recognizer? This property controls if touches are delivered to any underlying views. So settings this to NO should allow the touch to be sent to your button. More detail here.
This property can be set in code or in Interface Builder. You can set it in IB by highlighting the gesture recognizer and uncheck "Cancels Touches in View" (I think) in the Attributes Inspector.
I'm not in front of a Mac currently so I can't confirm the exact wording.
Instead of showing the keyboard I want to display a popover view when a textField is selected (my code is at the bottom). If the keyboard isn't showing then everything works great. However, if the keyboard is showing and then the textfield is selected the keyboard doesn't get dismissed, somewhere the firstResponders must be getting lost but I don't know where. Does anyone have a solution to this?
My textfield:
self.startDateTextField = [[UITextField alloc] initWithFrame:CGRectMake(79, 148, 138, 27)];
[self.startDateTextField setBorderStyle:UITextBorderStyleRoundedRect];
[self.startDateTextField setDelegate:delegate];
[self.startDateTextField addTarget:delegate action:#selector(editStartDate:) forControlEvents:UIControlEventEditingDidBegin];
[popoverWrapper addSubview:self.startDateTextField];
and in editStartDate: I have:
-(void)editStartDate:(UITextField *)textField {
[textField resignFirstResponder];
DatePickerVC *datePickerVC = [[DatePickerVC alloc] init];
datePickerVC.delegate = self;
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:datePickerVC];
[self.popoverController setDelegate:self];
[self.popoverController presentPopoverFromRect:CGRectMake(0, 0, 5, 5) inView:textField permittedArrowDirections:UIPopoverArrowDirectionRight animated:YES];
}
This is very easy to do, use your UITextFieldDelegate methods specifically UITextFieldShouldBeginEditing and return NO and execute the code to show the popover instead. This way the keyboard is never shown to begin with.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
[self.view endEditing:YES]; // added this in for case when keyboard was already on screen
[self editStartDate:textField];
return NO;
}
for it to work make sure you set the delegate of the textField to self (the view controller) and in your editStartDate method remove the resignFirstResponder call.
Try like this in your editStartDate: method
[self.startDateTextField resignFirstResponder];
EDIT:
But instead of doing resign the keyboard when you click in textfield, you can make something like setInputView for Textfield to bring out the popViewController.
DatePickerVC *datePickerVC = [[DatePickerVC alloc] init];
datePickerVC.delegate = self;
self.popoverController = [[UIPopoverController alloc] initWithContentViewController:datePickerVC];
[self.popoverController setDelegate:self];
[self.popoverController presentPopoverFromRect:CGRectMake(0, 0, 5, 5) inView:textField permittedArrowDirections:UIPopoverArrowDirectionRight animated:YES];
self.startDateTextField = [[UITextField alloc] initWithFrame:CGRectMake(79, 148, 138, 27)];
[self.startDateTextField setBorderStyle:UITextBorderStyleRoundedRect];
[self.startDateTextField setDelegate:delegate];
self.startDateTextField.inputView = self.popoverController;
You appear to be resigning the first responder of the text field; however, this isn't necessarily the first responder, which may explain why calling it has no effect.
Instead, you should use the endEditing category to recurse through all children of your view to resign the first responder from whichever view it is attached to:
[self.view endEditing:YES];
In any case, as you never want to show the keyboard, you can simply implement UITextFieldDelegate to override the default behaviour.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
// Your popover code.
return NO;
}
UITextFieldDelegate Protocol Reference.
So you're trying to hide the keyboard immediately after the text field is selected and display something else?
There's two things I can think of:
Give the text field some time to get it together before resigning the first responder:
[textField performSelector:#selector(resignFirstResponder) withObject:nil afterDelay:0.5];
Set the inputView property of the text field to nil or a UIView with a clear background color.
If inputView on the text field doesn't get you what you want, you can also simply put an invisible button on top of the UITextField and just show the popover from the button's action. To the user it will appear as if the text field brought up the popover. If at that point the keyboard is still there, call resignFirstResponder on all possible first responders (text fields etc.).