How to Navigate Through Textfields (Next/Done Buttons)

How to navigate through textfields (Next / Done Buttons)

In Cocoa for Mac OS X, you have the next responder chain, where you can ask the text field what control should have focus next. This is what makes tabbing between text fields work. But since iOS devices do not have a keyboard, only touch, this concept has not survived the transition to Cocoa Touch.

This can be easily done anyway, with two assumptions:

  1. All "tabbable" UITextFields are on the same parent view.
  2. Their "tab-order" is defined by the tag property.

Assuming this you can override textFieldShouldReturn: as this:

-(BOOL)textFieldShouldReturn:(UITextField*)textField
{
NSInteger nextTag = textField.tag + 1;
// Try to find next responder
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (nextResponder) {
// Found next responder, so set it.
[nextResponder becomeFirstResponder];
} else {
// Not found, so remove keyboard.
[textField resignFirstResponder];
}
return NO; // We do not want UITextField to insert line-breaks.
}

Add some more code, and the assumptions can be ignored as well.

Swift 4.0

 func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
// Try to find next responder
let nextResponder = textField.superview?.viewWithTag(nextTag) as UIResponder!

if nextResponder != nil {
// Found next responder, so set it
nextResponder?.becomeFirstResponder()
} else {
// Not found, so remove keyboard
textField.resignFirstResponder()
}

return false
}

If the superview of the text field will be a UITableViewCell then next responder will be

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!

How to navigate through SwiftUI TextFields by clicking on return button from keyboard?

To resolve your two problems, you need to work with UIKit from SwiftUI. First, you need to customized TextField using UIViewRepresentable. Here is the sample code for test purposes though the code is not so elegance. I bet, there will be having a more robust solution.

  1. Inside the customized TextFieldType, the Keyboard return type has
    been set.
  2. By using object binding and delegate methods
    textFieldShouldReturn, View can focus the keyboard by updating the binding variables.

Here is the sample code:

import SwiftUI

struct KeyboardTypeView: View {
@State var firstName = ""
@State var lastName = ""
@State var focused: [Bool] = [true, false]

var body: some View {
Form {
Section(header: Text("Your Info")) {
TextFieldTyped(keyboardType: .default, returnVal: .next, tag: 0, text: self.$firstName, isfocusAble: self.$focused)
TextFieldTyped(keyboardType: .default, returnVal: .done, tag: 1, text: self.$lastName, isfocusAble: self.$focused)
Text("Full Name :" + self.firstName + " " + self.lastName)
}
}
}
}



struct TextFieldTyped: UIViewRepresentable {
let keyboardType: UIKeyboardType
let returnVal: UIReturnKeyType
let tag: Int
@Binding var text: String
@Binding var isfocusAble: [Bool]

func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.keyboardType = self.keyboardType
textField.returnKeyType = self.returnVal
textField.tag = self.tag
textField.delegate = context.coordinator
textField.autocorrectionType = .no

return textField
}

func updateUIView(_ uiView: UITextField, context: Context) {
if isfocusAble[tag] {
uiView.becomeFirstResponder()
} else {
uiView.resignFirstResponder()
}
}

func makeCoordinator() -> Coordinator {
Coordinator(self)
}

class Coordinator: NSObject, UITextFieldDelegate {
var parent: TextFieldTyped

init(_ textField: TextFieldTyped) {
self.parent = textField
}

func updatefocus(textfield: UITextField) {
textfield.becomeFirstResponder()
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {

if parent.tag == 0 {
parent.isfocusAble = [false, true]
parent.text = textField.text ?? ""
} else if parent.tag == 1 {
parent.isfocusAble = [false, false]
parent.text = textField.text ?? ""
}
return true
}

}
}

Output:
Sample Image

How to navigate through UITextFields (Next / Done Buttons) with Rx

With a simple UITextField extension you can observe the textfield's control events and act accordingly.

Extension:

import UIKit
import RxSwift
import RxCocoa

extension UITextField {
func resignWhenFinished(_ disposeBag: DisposeBag) {
setNextResponder(nil, disposeBag: disposeBag)
}

func setNextResponder(_ nextResponder: UIResponder?, disposeBag: DisposeBag) {
// Set the return key type to:
// - next: When there is a next responder
// - done: When there is no next responder (simply resign)
returnKeyType = (nextResponder != nil) ? .next : .done

// Subscribe on editing end on exit control event
rx.controlEvent(.editingDidEndOnExit)
.subscribe(onNext: { [weak self, weak nextResponder] in
if let nextResponder = nextResponder {
// Switch to next responder if available
nextResponder.becomeFirstResponder()
} else {
// Otherwise simply resign
self?.resignFirstResponder()
}
})
.addDisposableTo(disposeBag)
}
}

Usage:

@IBOutlet private weak var firstTextField: UITextField!
@IBOutlet private weak var secondTextField: UITextField!
@IBOutlet private weak var lastTextField: UITextField!
private let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()

firstTextField.setNextResponder(secondTextField, disposeBag: disposeBag)
secondTextField.setNextResponder(lastTextField, disposeBag: disposeBag)
lastTextField.resignWhenFinished(disposeBag)
}

How to navigate through textfields (Next / Done Buttons)

In Cocoa for Mac OS X, you have the next responder chain, where you can ask the text field what control should have focus next. This is what makes tabbing between text fields work. But since iOS devices do not have a keyboard, only touch, this concept has not survived the transition to Cocoa Touch.

This can be easily done anyway, with two assumptions:

  1. All "tabbable" UITextFields are on the same parent view.
  2. Their "tab-order" is defined by the tag property.

Assuming this you can override textFieldShouldReturn: as this:

-(BOOL)textFieldShouldReturn:(UITextField*)textField
{
NSInteger nextTag = textField.tag + 1;
// Try to find next responder
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (nextResponder) {
// Found next responder, so set it.
[nextResponder becomeFirstResponder];
} else {
// Not found, so remove keyboard.
[textField resignFirstResponder];
}
return NO; // We do not want UITextField to insert line-breaks.
}

Add some more code, and the assumptions can be ignored as well.

Swift 4.0

 func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
// Try to find next responder
let nextResponder = textField.superview?.viewWithTag(nextTag) as UIResponder!

if nextResponder != nil {
// Found next responder, so set it
nextResponder?.becomeFirstResponder()
} else {
// Not found, so remove keyboard
textField.resignFirstResponder()
}

return false
}

If the superview of the text field will be a UITableViewCell then next responder will be

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!

navigate through textfields

I don't like solutions that incorporate the tag. Instead I would put all inputfileds in the desired order into an array and in -textFieldShouldReturn: use the given textfield to get it's index from in the array. Then I would get the object at that index.

- (BOOL)textFieldShouldReturn:(UITextField*)textField {
NSUInteger nextIndex = [arrayWithResponders indexOfObject:textField]+1 % [arrayWithResponders count];
UIResponder* nextResponder = [arrayWithTextFields objectAtIndex: nextIndex];

if (nextResponder) {
[nextResponder becomeFirstResponder];
} else {
[textField resignFirstResponder];
}
return NO;
}

You just added, that the breakpoints aren't triggered, so most likely you didn't set up the delegate.

Next/Done button using Swift with textFieldShouldReturn

I was attempting to test my textfields in the SignUpWindowView.swift, which is where all of the textFields are created. But, since I place SignUpWindowView into my MainViewController as a subview, all of my UITextField "handling" needed to be done in the MainView and NOT its subview.

So here is my entire code (at the moment) for my MainViewController, which handles moving my SignUpWindowView up/down when the keyboard is shown/hidden and then moves from one field to the next. When the user is in the last text field (whose keyboard Next button is now set to Done in the subview) the keyboard tucks away and the user can then submit the form with a signup button.

MainViewController:

import UIKit

@objc protocol ViewControllerDelegate
{
func keyboardWillShowWithSize(size:CGSize, andDuration duration:NSTimeInterval)
func keyboardWillHideWithSize(size:CGSize,andDuration duration:NSTimeInterval)
}

class ViewController: UIViewController,UITextFieldDelegate
{
var keyboardDelegate:ViewControllerDelegate?

let signUpWindow=SignUpWindowView()
let signUpWindowPosition:CGPoint=CGPointMake(505, 285)

override func viewDidLoad()
{
super.viewDidLoad()

// Keyboard Notifications
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)

// set the textFieldDelegates
signUpWindow.firstNameTextField.delegate=self
signUpWindow.lastNameTextField.delegate=self
signUpWindow.userNameTextField.delegate=self
signUpWindow.passwordTextField.delegate=self
signUpWindow.confirmPasswordTextField.delegate=self
signUpWindow.emailTextField.delegate=self
}


func keyboardWillShow(notification: NSNotification)
{
var info:NSDictionary = notification.userInfo!
let keyboardFrame = info[UIKeyboardFrameEndUserInfoKey] as! NSValue
let keyboardSize = keyboardFrame.CGRectValue().size

var keyboardHeight:CGFloat = keyboardSize.height

let animationDurationValue = info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber

var animationDuration : NSTimeInterval = animationDurationValue.doubleValue

self.keyboardDelegate?.keyboardWillShowWithSize(keyboardSize, andDuration: animationDuration)

// push up the signUpWindow
UIView.animateWithDuration(animationDuration, delay: 0.25, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.signUpWindow.frame = CGRectMake(self.signUpWindowPosition.x, (self.signUpWindowPosition.y - keyboardHeight+140), self.signUpWindow.bounds.width, self.signUpWindow.bounds.height)
}, completion: nil)
}

func keyboardWillHide(notification: NSNotification)
{
var info:NSDictionary = notification.userInfo!

let keyboardFrame = info[UIKeyboardFrameEndUserInfoKey] as! NSValue
let keyboardSize = keyboardFrame.CGRectValue().size

var keyboardHeight:CGFloat = keyboardSize.height

let animationDurationValue = info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber

var animationDuration : NSTimeInterval = animationDurationValue.doubleValue

self.keyboardDelegate?.keyboardWillHideWithSize(keyboardSize, andDuration: animationDuration)

// pull signUpWindow back to its original position
UIView.animateWithDuration(animationDuration, delay: 0.25, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.signUpWindow.frame = CGRectMake(self.signUpWindowPosition.x, self.signUpWindowPosition.y, self.signUpWindow.bounds.width, self.signUpWindow.bounds.height)
}, completion: nil)
}

func textFieldShouldReturn(textField: UITextField) -> Bool
{
switch textField
{
case signUpWindow.firstNameTextField:
signUpWindow.lastNameTextField.becomeFirstResponder()
break
case signUpWindow.lastNameTextField:
signUpWindow.userNameTextField.becomeFirstResponder()
break
case signUpWindow.userNameTextField:
signUpWindow.passwordTextField.becomeFirstResponder()
break
case signUpWindow.passwordTextField:
signUpWindow.confirmPasswordTextField.becomeFirstResponder()
break
case signUpWindow.confirmPasswordTextField:
signUpWindow.emailTextField.becomeFirstResponder()
break
default:
textField.resignFirstResponder()
}
return true
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}

@IBAction func signup()
{
signUpWindow.frame=CGRectMake(signUpWindowPosition.x, signUpWindowPosition.y, 485,450)
signUpWindow.backgroundColor=UIColor.clearColor()

self.view.addSubview(signUpWindow)
}
}

set next Button & Done Button instead of return in keyboard swift 4

You can register to keyboard appear/disappear events and can add a UIToolBar with your next & back buttons on top of it. Or else, just integrate this library and you're done with few lines of code.

Switching between Text fields on pressing return key in Swift

Make sure your UITextField delegates are set and the tags are incremented properly. This can also be done through the Interface Builder.

Here's a link to an Obj-C post I found: How to navigate through textfields (Next / Done Buttons)

class ViewController: UIViewController,UITextFieldDelegate {
// Link each UITextField (Not necessary if delegate and tag are set in Interface Builder)
@IBOutlet weak var someTextField: UITextField!

override func viewDidLoad() {
super.viewDidLoad()
// Do the next two lines for each UITextField here or in the Interface Builder
someTextField.delegate = self
someTextField.tag = 0 //Increment accordingly
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Try to find next responder
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
// Not found, so remove keyboard.
textField.resignFirstResponder()
}
// Do not add a line break
return false
}
}

Next button don't navigate through UITextField

The problem is the table. When you ask you text view for it's superview, it will return the cell, which doesn't contain any other siblings. Try:

UIResponder* nextResponder = [tableView viewWithTag:nextTag];


Related Topics



Leave a reply



Submit