iOS: automatic way to tab between text fields - ios

I have a relatively large iPhone application with many views.
I would like implement a next/previous option on my keyboard.
I have managed to implement it UI-wise, with some code examples i saw online, but all of them are assuming we need to add code to each view controller to implement the actual transition between the text fields.
My question is: is there a general way to know, given some text field, who is the next field in order? (i.e without refactoring each of my view controllers)
I ask this question because when i use the iPhone simulator and press the computer's Tab key - the switch between the fields happen, so i wonder if there is a built-in or generic way to implement it on iOS.
clarification:
is there a way of doing it without adding a specific code for each type of view controller? (adding a generic code is acceptable)

I want to write how i solved this problem, with the help of many good answers given to me here :)
First, i could not create fully generic code that creates tab regardless of the view it is in.
Instead i created this thing, which i think is the most generic solution with the firstResponder method not working:
i created custom toolbar with my next/previous/done buttons and appropriate actions delegate.
than i extended UIViewController by adding category "Tab". this category declares a fieldsArray and implements the delegate method.
Now what every specific view controller needs to do (beside importing the category) is to provide this fieldsArray according to its properties and calling the init method which adds the buttons toolbar to this fields
I hope you could benefit from this, and again thanks for all the good answers

you could have a method in a utility class that takes as arguments a textfield and a viewcontroller. then you could use the "tag"-attribute of the textfields to find the next textfield in that viewcotroller, assuming that you assigned the tags accordingly. numbers would be great, i think. a simple callback method in the vc could handle the focus-change. thats about as generic as i can see right now.

This is some generic code that I came up with:
// add a property for the fieldsArray
//add this in viewDidLoad
_fieldsArray = [[NSMutableArray alloc] init];
NSArray *viewsArray = [self.view subviews];
for (id view in viewsArray) {
if ([view isKindOfClass:NSClassFromString(#"UITextField")]) {
[_fieldsArray addObject:view];
}
}
//add this in your action that switches the fields
for (UITextField *field in _fieldsArray) {
if ([field isFirstResponder]) {
if ([fieldsArray lastObject] == field) {
[_fieldsArray[0] becomeFirstResponder];
}else {
NSUInteger nextIndex = [_fieldsArray indexOfObject:field] + 1;
[_fieldsArray[nextIndex] becomeFirstResponder];
}
break;
}
}
Before using it it should be improved.
1) find all subviews of self.view recursively
2) do some checks if the arrays are empty or nil or have just one object in them.
Good luck!

Related

Why can't I CTRL drag an action for storyboard?

I am new to iOS programming and using iOS 6. I see that, using XCode I can CTRL+drag action and outlets for button sand text field but not for the storyboard. I want to do some action when user clicks on the storyboard (taps away from the text field).
Here is my code:
- (IBAction)editingEnded:(UITextField *)sender {
NSLog(#"%#", #"in editingEnded");
[sender resignFirstResponder];
}
- (IBAction)buttonSelected:(UIButton *)sender {
if(_firstClick) {
[_textField resignFirstResponder];
}
}
I guess, you asked me to implement something like editingEnded? This is my delegate for editing did end action (how can I confirm this, there is no such annotation/attribute attached to this method?). However, this method alone didn't work. When i added the 2nd method buttonSelected as a delegate for another button on the story board, then editingEnded is also called due to [_textField resignFirstResponder];.
A storyboard is a container which contains your various UI elements, including your text field.
What you really want to do is set a delegate for your text field, which you can do with your view controller.
Then, when you click away from the text field, you can catch that happening via the delegate method "textFieldDidEndEditing:". You implement that function in your view controller, make certain your text field has your view controller set as the delegate, and you should be able to do whatever you want within your view controller's implementation of the "textFieldDidEndEditing:" function.

Hide or temporarily remove a child ViewController from a parentViewController?

(asking and self-answering, since I found no hits on Google, but managed to find a solution in the end by trial and error)
With iOS 5 and 6, Apple added some ugly hacks to make InterfaceBuilder support "embedded" viewcontrollers. They didn't document how those work, they only give code-level examples, and they only cover a limited subset of cases.
In particular, I want to have an embedded viewcontroller that is sometimes hidden - but if you try the obvious approach it doesn't work (you get a white rectangle left behind):
childViewController.view.hidden = TRUE;
Why don't you just create an IBOutlet to your container view and do
self.containerView.hidden = YES;
How they've done it appears to be a variation on the manual way that worked since iOS 2 (but which only supported views, not viewcontrollers) - there is a real, genuine UIView embedded into the parent (not mentioned in the source code examples - it's only added when you use InterfaceBuilder!).
So, instead, if you do:
childViewController.view.superview.hidden = TRUE;
...it works!
Also, counterintuitively, you can call this method at any time from viewDidLoad onwards - the "embed segue" hack from Apple is executed before viewDidLoad is called.
So you can do this on startup to have your childViewController start off invisible.
Use This [self.childviewController setHidden:YES];
In case somebody will need to hide/show all child views or iterate over them:
func hideChildrenViews() {
for view in self.view.subviews {
(view as! UIView).hidden = true
}
}
func showChildViews() {
for view in self.view.subviews {
(view as! UIView).hidden = false
}
}

TableView and PKRevealController gesture conflicts - How to really solve them?

I am trying to implement a view with sliding side menus, such as with PKRevealController in iOS 6.1. A simple demo of this issue with source code on github is here, however you might not need to grab it if you already understand gestureRecognizer delegate implementation.
The problem I see is that two gestures that my users will want to use are going to be mutually confused for each other. The UITableView in the center (main screen) of the application should be able to use the swipe-right gesture to delete, but I still want a swipe that occurs across the top navigation area to result in exposing the side menus.I also intend to show other things than just the Table view, and at runtime I plan to swap out the main view with a different view, whenever a user selects a button on one of the side menus. This is kind of like a "hidden side tray UITabBarController" that I'm going for, but I want the side bars to be revealed only when the main "front view" controller is NOT a UITableView or its subviews.
Right now, using the demo sources that comes with PKRevealController, and adding deletion support to the main view's UITableView, no slide gesture to delete a row is possible. (You have to add one table view method to enable deletion support in the UITable view, which I did add.)
This was asked here, but the answer stated is incomplete, and as seen below, does not work for me and I have no idea why, because it appears that this delegate method is not invoked at any time where I return a YES, and yet it goes ahead anyways and begins a gesture.
Update The answer in the previous question is also wrong, as compared to the WIKI/FAQ answer I placed below.
I have only modified the class PKRevealController.m by adding this:
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
UIView *view1 = otherGestureRecognizer.view;
UIView *view2;
if (view1) {
view2 = view1.superview;
};
if ([gestureRecognizer.view isKindOfClass:[UITableView class]])
{
return NO;
}
// Co-operate by not stealing gestures from UITableView.
if ([view1 isKindOfClass:[UITableView class]]) {
return NO;
}else if ([view1 isKindOfClass:[UITableViewCell class]]) {
return NO;
// UITableViewCellContentView
}
else if (view2 && [view2 isKindOfClass:[UITableViewCell class]]) {
return NO;
// UITableViewCellContentView
}
else
{
return YES; // NEVER GETS HIT. BREAKPOINT HERE!
}
}
What confuses me is that at no point does the return YES code above get hit (I have a breakpoint on it) and yet, the Gesture controller is still stealing the gesture.
Note: I have made an evil hack, but I thought that I could prevent this cleanly. Here is my evil hack:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.revealPanGestureRecognizer)
{
CGPoint translation = [self.revealPanGestureRecognizer translationInView:self.frontViewContainer];
BOOL begin = (fabs(translation.x) >= fabs(translation.y));
// BEGIN EVIL HACK
if (_topLimitY > 0) {
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
if (location.y>_topLimitY) // _topLimitY = 55 for instance.
begin = NO;
}
// END EVIL HACK.
return begin;
}
else if (gestureRecognizer == self.revealResetTapGestureRecognizer)
{
return ([self isLeftViewVisible] || [self isRightViewVisible]);
}
return YES;
}
Right now in my evil hacked demo, I have set the topLimitY property (that I added to PKRevealController's properties) to 55, which allows me to swipe on the nav bar area of the front view, but not on the table view which takes up the rest of the demo.
Note that I plan to have multiple main views, and only want to defeat the gesture recognition on the whole main area if the view is a UITableView or some sub-view thereof. That is why I call my hack above a hack. Because I thought you could tell the gesture recognizer to go away and not bother you, and yet it doesn't work, it doesn't even invoke the shouldRecognize method, it just goes ahead and does the next thing in its list of things to do.
I should really read the WIKI first shouldn't I?
This is a FAQ, it says so right here:
When instantiating the controller pass this option in your options dictionary:
NSDictionary *options = #{
PKRevealControllerRecognizesPanningOnFrontViewKey : #NO
};
This will disable pan-based reveal for the entire front view. Now, you can use the revealPanGestureRecognizer and add it to any view you desire to be panned on that doesn't interfere with your table view, to enable gesture based reveal.
I'd advise (if working with a table based environment with swipe'able cells) you, to add the revealPanGestureRecognizer to your front view controller's navigation bar (which it most likely has):
[self.navigationController.navigationBar addGestureRecognizer:self.revealController.revealPanGestureRecognizer];
And voilĂ  - panning doesn't interfere with your table view anymore.
more info at:
https://github.com/pkluz/PKRevealController/issues/76
Thank you Wiki. If only I had read it all first.
The above completely answers my question and was already there on the wiki. I'm answering my own question because it seems Google always comes to Stackoverflow first, and that might help other confused developers in the future.
Update If the above thing blows up when you try it, it's probably being done too early. Here's a slightly more robust version of the above fix:
// Additional gesture recognition linkups. The underscore variables here
// are implementation-section ivars in my app-delegate, that I have already
// checked are valid and initialized, and this is the last thing in my app delegate
// didFinishLaunch... method, before the return YES:
UIGestureRecognizer *rec = _revealController.revealPanGestureRecognizer;
if (rec) {
[_frontViewNavController.navigationBar addGestureRecognizer:rec];
}
Use This :
self.revealController.frontViewController.revealController.recognizesPanningOnFrontView = YES;

iOS code organizing pattern

I'm fairly new to iOS development. I normally create all my UIViews, UILabels, UIButtons etc. in code instead of using the Interface Builder. This makes it very easy for my UIViewControllers to get extremely large and hard to follow, mixing outlet declarations with actual actions and logic.
- (UIButton *) continueButton {
// if button is nil
if(_continueButton == nil) {
UIButton *button = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
/* more configuration */
_continueButton = button;
}
return _continueButton;
}
// occures when continue button is tapped
- (void) buttonTouch:(id) sender
{
UIButton *button = ((UIButton *)sender);
if(button == continueButton) {
/* do stuff */
}
}
What i want to accomplish is to separate the actual actions, animations, logic and so on,
and store the buttons, labels and other views' declaration in a different file.
As a solution I was thinking of creating another UIViewController with the outlets and embedding it as a child inside the main one which holds the logic, using "addChildViewController".
Would this be the right way to go ? How do you handle it?
I think you are going about this the wrong way. Using IB is a smart thing to do in most cases. Xcode will create the code for the UI elements more efficiently then you will.
Also, using IB AND the Assistant Editor will make wiring things up much faster and more efficient. It also ensures that cleanup code is added where needed.
For code separation within my implementation file, I personally use #pragma mark - to separate my Outlet declarations from my Action declarations and then always put the methods for the Actions separate (usually at the top) from the other methods.
I also declare the properties for Outlets privately unless for some reason they need to be public.
My structure:
My methods at the top
Actions next
My delegate methods
Apple's Methods

How do you set the tab order in iOS?

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.

Resources