I have implemented the standard 'next' feature on UITextFields in my app (pressing next will move you to the next appropriate text field). Most of the time this works well and will move to the appropriate textfield no problem. However on a few occasions for some reason the textField will return NO when i ask it to become the first responder and I am not sure why.
Some information about the textfields/view:
The view has multiple UITableViews, some of the UITableViewCells in these have the UITextfields i want to become first responder in them, others do not.
When the tables are created i loop through the main views subviews to find all the first responders and add them to an array of first responders (_firstResponders - variable name).
Each cell calls back via a delegate to the main view when the next responder is wanted.
Below is the code i use to get the next responder:
//get the current first responders index
NSInteger currentIndex = [_firstResponders indexOfObject:currentResponder];
// if the current index plus 1 is less than the overall count of first responders on this view then continue, else resign the responder as no more are avilable
if((currentIndex+1) < [_firstResponders count])
{
// get the potential next responder
UIResponder *nextResponder = [_firstResponders objectAtIndex:currentIndex+1];
// if it can become the first responder assign it,
// else callback into this function with the unavilable first responder
if([nextResponder canBecomeFirstResponder])
{
[currentResponder resignFirstResponder];
BOOL becomeFirstResponder = [nextResponder becomeFirstResponder];
if(becomeFirstResponder == NO)
{
DLog(#"TextField at index: %d has returned no to becoming first responder", (currentIndex+1));
}
}
else
[self getNextResponder:nextResponder];
}
else
[currentResponder resignFirstResponder];
}
As you can see i check that it is available to become the first responder in code and i have also checked that the textfield is enabled manually (I also check this at the time of adding the responder to the array of responders). It is also worth mentioning i have checked the array size and the responder to be set when this occurs and they are correct/valid.
The strangest part for me about this is that I had only previously seen this if I did not call resignFirstResponder on the current responder before setting the new responder.
Does anyone know the reason for it returning NO/how i can fix this?
Thanks in advance!
Related
I have a bunch of text fields in a registration form that are organized in two vertical stack views in a XIB file. I noticed that when running the app in the simulator I can press the Tab key and iOS will automatically move on to the next text field.
But this doesn't work sometimes, and I was wondering why. Sometimes the system focuses a text field from another stack view instead of the field below it.
I have already set up a chain of Next text fields (when pressing Next on the software keyboard) like this:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSUInteger index = [self.textFields indexOfObject:textField];
if (index != NSNotFound) {
if (index == self.textFields.count - 1) {
[textField resignFirstResponder];
[self createAccount];
} else {
UITextField *nextTextField = self.textFields[index + 1];
[nextTextField becomeFirstResponder];
}
return NO;
}
return YES;
}
textFields is an array of all text fields that this screen has from top to bottom.
I just want to be able to fill the form fast by typing a few characters and pressing Tab without additional mouse clicks. Is it possible?
It seems that -textFieldShouldReturn is not called during this "tab switch" so I can't control which text field becomes the next first responder. What is the trick for getting them to focus in the right order?
Check the index that returns by indexOfObject:. If you have some equal objects in the array, this method returns the index of the first of them.
If the index is wrong, use the tag property of UITextField like this:
UITextField * nextResponder = [self.textFields objectAtIndex:textField.tag + 1];
or if all of your text fields are in the same superview:
UITextField * nextResponder = (UITextField*)[textField.superview viewWithTag:textField.tag + 1];
Apple documentation:
Starting at index 0, each element of the array is passed as an argument to an isEqual: message sent to anObject until a match is found or the end of the array is reached. Objects are considered equal if isEqual: (declared in the NSObject protocol) returns YES.
https://developer.apple.com/documentation/foundation/nsarray/1417076-indexofobject?language=objc
This method defines what it means for instances to be equal. For example, a container object might define two containers as equal if their corresponding objects all respond YES to an isEqual: request. See the NSData, NSDictionary, NSArray, and NSString class specifications for examples of the use of this method.
If two objects are equal, they must have the same hash value. This last point is particularly important if you define isEqual: in a subclass and intend to put instances of that subclass into a collection. Make sure you also define hash in your subclass.
https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc
I've got 2 UITextFields. I can detect when the user in inside either of them with:
textFieldDidBeginEditing:(UITextField *)textField
Which works great. But i want the textfields to animate out when the user is not inside either of them. Currently I'm calling: (void)textFieldDidEndEditing:(UITextField *)textField
However, this is called even if i switch from on textfield to the other. Is there a better way to call do this?
What I would do is add a BOOL flag to detect if you should animate out your text fields or not. It would work something like this:
-(void)hideTextFields {
if (self.shouldHideTextFields) {
self.textField1.hidden = YES; // Or whatever you want to do with
self.textField2.hidden = YES; // your text fields
}
}
Declare a method that checks the BOOL flag and decides whether to hide or not the text fields
Whenever either of the textFieldDidBeginEditing:(UITextField *)textField methods are called set the BOOL flag (you can call it 'shouldHideTextFields') to NO.
Whenever either of the textFieldDidEndEditing:(UITextField *)textField methods are called set the BOOL flag to 'YES'. Also, call [self performSelector:#selector(hideTextFields) withObject:nil afterDelay:1]; to give the user a little time to select the other text field. If he/she does, the flag will be set to NO thanks to the above step.
Hope this helps!
On didend check if either of your textviews is currently the first responder with [textfield isFirstResponder] or [textfield isEditing]. There may be a slight delay when one ends and the other takes control. If that's the case then you could do this check after a slight delay using performSelector:afterDelay.
On textFieldDidEndEditing delegate method, do not just perform your disappearing animations, but do that in dispatch_async, checking if there is no textField editing right now.
In case if user just ended editing of one text field, there will be no editing textfield. But if user had switched to another textfield, it will already start editing and it can be easily checked by isFirstResponder method.
I am using a subclass of a scrollview that moves the keyboard out of the way. (TPKeyboardAvoidingScrollView)
I think this is conflicting with my implementation of the next and previous buttons. I have built an inputaccessoryview
I have a category that sets the next and previous textfields for each field
when i edit a textfield, i set the current, previous and next textfields
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
_currentTextField = textField;
_prevTextField = [textField prevTextField];
_nextTextField = [textField nextTextField];
return YES;
}
when they click next or previous i call this method
- (void)selectAdjacentResponder:(id)sender{
UISegmentedControl *segmented = sender;
if(segmented.selectedSegmentIndex == 0){
[_prevTextField becomeFirstResponder];
} else {
[_nextTextField becomeFirstResponder];
}
}
this works fine.. but when i close the keyboard. my scrollview is messed up. if i add the line
[_currentTextField resignFirstResponder];
to the first line of my selectadjacent method it solves the problem. but the problem is it makes the screen focus in a funky way since i'm dismissing and accessing the next textfield at the same time.
i have tried resigning first responder when i close the keyboard. but i think my scrollview is calculated before that point.. any idea what to do =/
i am using a uitableview with one section and five cells, and two different uitextfields on each tableCell.
my problem is: when a user tabs on first textfield on first table row, keyboard comes up, then the user taps on the second textfield. the keyboard DISMISS and SHOW.
so how can i keep the keyboard up instead of DISMISS and SHOW when the user switching focus on textfields?
thanks.
first edit:
sorry for the late response on this, the code is a bit complicated to show in here. i do not resignFirstResponder/becomeFirstResponder in any of the textField delegate methods. could you please throw in any ideas on top of your head. thanks for all your help.
second edit:
sorry for my bad mistake, i totally misunderstood my problem. i will relink the new post in here in a minute.
third edit:
this is my new question
This should work for you... Just a guess work from my side as you had not posted any code:
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
if(textField == firstTextfield)
{
if([[firstTextfield stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0)
return NO;
[firstTextfield resignFirstResponder];
[secondTextField becomeFirstResponder];
return YES;
}
else if(textField == secondTextField)
{
//Anything u want here
return YES;
}
return NO;
}
Normally the keyboard doesn't dismiss on its own when you switch fields.
If you are using a textfield delegate and are responding to "editingDidEnd" by resigningFirstResponder, then you will see that behavior.
If that is the case, after you leave the field, it is calling one of the methods and resigning the keyboard, then when you touch the other field, it is calling the firstResponder.
So, look for some code where you are setting a textfield delegate to call one of the textfield selector methods. Find the method that is being called and see if it is resigingFirstResponder.
If it is, then you may need to remove it or wrap it in some appropriate logic.
Is there a way (either in IB or code) to set the tab order between text fields in a view?
Note that I'm not talking about the next form field after the return (or "Next") button is pressed -- many bluetooth keyboards have a tab key, which seems to cycle through the fields in completely different order. In my particular case, this order doesn't correspond to the fields' position in the view or even the order in which the fields were added. Modifying the xib file by hand to change the NSNextKeyView doesn't seem to make a difference either.
Does anyone know how to change this order?
#sprocket's answer was only somewhat helpful. Just because something works out of the box doesn't mean you should stop thinking about a better way -- or even the right way -- of doing something. As he noticed the behavior is undocumented but fits our needs most of the time.
This wasn't enough for me though. Think of a RTL language and tabs would still tab left-to-right, not to mention the behavior is entirely different from simulator to device (device doesn't focus the first input upon tab). Most importantly though, Apple's undocumented implementation seems to only consider views currently installed in the view hierarchy.
Think of a form in form of (no pun intended) a table view. Each cell holds a single control, hence not all form elements may be visible at the same time. Apple would just cycle back up once you reached the bottommost (on screen!) control, instead of scrolling further down. This behavior is most definitely not what we desire.
So here's what I've come up with. Your form should be managed by a view controller, and view controllers are part of the responder chain. So you're perfectly free to implement the following methods:
#pragma mark - Key Commands
- (NSArray *)keyCommands
{
static NSArray *commands;
static dispatch_once_t once;
dispatch_once(&once, ^{
UIKeyCommand *const forward = [UIKeyCommand keyCommandWithInput:#"\t" modifierFlags:0 action:#selector(tabForward:)];
UIKeyCommand *const backward = [UIKeyCommand keyCommandWithInput:#"\t" modifierFlags:UIKeyModifierShift action:#selector(tabBackward:)];
commands = #[forward, backward];
});
return commands;
}
- (void)tabForward:(UIKeyCommand *)command
{
NSArray *const controls = self.controls;
UIResponder *firstResponder = nil;
for (UIResponder *const responder in controls) {
if (firstResponder != nil && responder.canBecomeFirstResponder) {
[responder becomeFirstResponder]; return;
}
else if (responder.isFirstResponder) {
firstResponder = responder;
}
}
[controls.firstObject becomeFirstResponder];
}
- (void)tabBackward:(UIKeyCommand *)command
{
NSArray *const controls = self.controls;
UIResponder *firstResponder = nil;
for (UIResponder *const responder in controls.reverseObjectEnumerator) {
if (firstResponder != nil && responder.canBecomeFirstResponder) {
[responder becomeFirstResponder]; return;
}
else if (responder.isFirstResponder) {
firstResponder = responder;
}
}
[controls.lastObject becomeFirstResponder];
}
Additional logic for scrolling offscreen responders visible beforehand may apply.
Another advantage of this approach is that you don't need to subclass all kinds of controls you may want to display (like UITextFields) but can instead manage the logic at controller level, where, let's be honest, is the right place to do so.
I'm interested in solving the same problem, although so far the default order, which appears to be left to right, then top to bottom, is the one I want.
I tested the hypothesis that the cursor moves in depth-first order through the tree of subviews and superview, but that is not true. Changing the order of subviews without changing their location didn't change the order of fields traversed by tab presses.
One possibly useful feature is that the text field delegate's textFieldShouldBeginEditing method appears to be called for every text field in the application's window. If that returns NO, then the text field won't be chosen, so if you can define your desired order and make only the right one return YES, that might solve your problem.
This is how you set the tab order on iOS:
http://weaklyreferenced.wordpress.com/2012/11/13/responding-to-the-tab-and-shift-tab-keys-on-ios-5-ios-6-with-an-external-keyboard/
The Tab key behaviour in ios will be as follows:-
when u press tab on external keyboard- the control traverses across all the textfields in that screen by calling only shouldBeginEditing method where its return value is also determined by Apple which cant be override.
After scanning all the fields it calculates nearest x positioned Textfield relative to view offset from the current Textfield and then nearest Y Positioned Field.
Also can't be done anything until control comes to textFieldDidBeginEditing method.
Reason for apple's restriction might be to let devs to follow the guidelines of UI where next responder of field should be it's closest positioned Field rather than any other field .
Register a UIKeyCommand to detect the tab key pressed. I did this in my current view controller.
self.addKeyCommand(UIKeyCommand(input: "\t", modifierFlags: [], action: #selector(tabKeyPressed)))
Inside the key tabKeyPressed handler find your current active field then set your next responder. orderedTextFields is an array of UITextField in the tab order I want.
func tabKeyPressed(){
let activeField = getActiveField()
if(activeField == nil){
return
}
let nextResponder = getNextTextField(activeField!)
nextResponder?.becomeFirstResponder()
}
func getActiveField() -> UITextField? {
for textField in orderedTextFields {
if(textField.isFirstResponder()){
return textField
}
}
return nil
}
func getNextTextField(current: UITextField) -> UITextField? {
let index = orderedTextField.indexOf(current)
if(orderedTextField.count-1 <= index!){
return nil
}
return orderedTextField[index! + 1]
}
You can do this by setting the tag for each textfield and handling this in the textfieldShouldReturn method.
See this blogpost about it:
http://iphoneincubator.com/blog/windows-views/how-to-create-a-data-entry-screen
The only way I've found to uniquely detect a Tab keystroke from a physical keyboard, is implementing the UIKeyInput protocol's insertText: method on a custom object that canBecomeFirstResponder.
- (void)insertText:(NSString *)text {
NSLog(#"text is equal to tab character: %i", [text isEqualToString:#"\t"]);
}
I didn't get this to work while subclassing UITextField, unfortunately, as UITextField won't allow the insertText: protocol method to get called.
Might help you on the way, though..
I solved this by subclassing UITextField as NextableTextField. That subclass has a property of class UITextField with IBOutlet a hookup.
Build the interface in IB. Set the class of your text field to NextableTextField. Use the connections Inspector to drag a connection to the 'next' field you want to tab to.
In your text field delegate class, add this delegate method...
- (BOOL)textFieldShouldReturn:(UITextField *) textField
{
BOOL didResign = [textField resignFirstResponder];
if (!didResign) return NO;
if ([textField isKindOfClass:[NextableTextField class]])
dispatch_async(dispatch_get_current_queue(), ^{ [[(NextableTextField *)textField nextField] becomeFirstResponder]; });
return YES;
}
BTW - I didn't come up with this; just remember seeing someone else's idea.