UITextField: When beginning input, textfield bounces up, and then bounces down - ios

I've seen this with UITextView too. Clearly, I'm missing something pretty basic.
I've tried:
- Setting textsize to small
- UITextField: Setting 1 line, no resize
- Removing the ability to move the texfield
import UIKit
class XViewController: UIViewController, UITextFieldDelegate, UIGestureRecognizerDelegate {
// UI elements
let cancelButton = UIButton()
let okButton = UIButton()
var image:UIImage?
var previewImageView:UIImageView = UIImageView()
let textField = UITextField()
let tapGestureRecognizer = UITapGestureRecognizer()
let textFieldTypingPositionY:CGFloat = 200
var textFieldActualPositionY:CGFloat!
let textFieldHeight:CGFloat = 40
var draggingTextField:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupTextField()
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString text: String) -> Bool {
let textFieldSize = textField.text!.characters.count
let textSize = text.characters.count
if text == "\n" && textFieldSize == 0 {
// press return on empty textfield
textField.text = ""
hideTextField()
return true
} else if textFieldSize == 1 && text == "" {
// backspace to empty
textField.text = ""
hideTextField()
return true
} else if text == "\n" {
textField.resignFirstResponder()
} else if (textFieldSize + textSize) > 30 {
let diff = (textFieldSize - textSize) - 30
textField.text = (text as NSString).substringToIndex(diff - 1)
}
return true
}
func setupTextField() {
// TextView
textField.translatesAutoresizingMaskIntoConstraints = false
textField.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.7)
textField.textColor = UIColor.whiteColor()
textField.font = UIFont.systemFontOfSize(14.0)
textField.textAlignment = NSTextAlignment.Center
textField.text = ""
textField.hidden = true
textField.delegate = self
self.view.addSubview(textField);
// Text view constraints
let leftConstraint = textField.leftAnchor.constraintEqualToAnchor(view.leftAnchor)
let rightConstraint = textField.rightAnchor.constraintEqualToAnchor(view.rightAnchor)
let heightConstraint = textField.heightAnchor.constraintEqualToConstant(textFieldHeight)
let verticalConstraint = textField.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 200)
NSLayoutConstraint.activateConstraints([leftConstraint, rightConstraint, heightConstraint, verticalConstraint])
// Tap gesture recognizer; if no text view is visible, shows text view at vertical location of tap
tapGestureRecognizer.delegate = self
tapGestureRecognizer.addTarget(self, action: "tapGesture:")
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapGesture(rec:UITapGestureRecognizer) {
// Show the textView in a location?
if textField.hidden == false {
if (textField.text!.characters.count == 0) {
hideTextField()
} else {
restoreTextFieldPosition()
}
} else {
moveTextFieldToEditingPosition()
}
}
func moveTextFieldToEditingPosition() {
// Textview animates into position a la snapchat
self.textField.userInteractionEnabled = false
textField.hidden = false
UIView.animateWithDuration(0.2, animations: { () -> Void in
self.textField.center.y = self.textFieldTypingPositionY
}, completion: { (Bool) -> Void in
self.textField.userInteractionEnabled = true
self.textField.becomeFirstResponder()
})
}
func hideTextField() {
// Hide text view and reset position
textField.resignFirstResponder()
textField.userInteractionEnabled = false
UIView.animateWithDuration(0.2, animations: { () -> Void in
}, completion: { (Bool) -> Void in
self.textField.hidden = true
})
}
func restoreTextFieldPosition() {
// Restore original position of textview after exiting keyboard
self.textField.userInteractionEnabled = false
textField.resignFirstResponder()
UIView.animateWithDuration(0.2, animations: { () -> Void in
}, completion: { (Bool) -> Void in
self.textField.userInteractionEnabled = true
})
}
}

I had this issue with iOS 9. It was ok on iOS 7 and 8. Basically I have a UITextField in a collection view cell. Sometimes when the user is done typing and editing ends, the text "bounces" up then down again into its correct position. Very strange and annoying glitch. Simply making this tweak fixed the issue on iOS 9 and proved to be safe on iOS 7 and 8:
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[textField layoutIfNeeded]; //Fixes iOS 9 text bounce glitch
//...other stuff
}

this is strange behaviour of textField. I have same issue and the reason is what you are changing the position of textField.
Solution
whatever you have write code for changing position (Y position in your case) using uiview.animation.... Remove this code
And i'm damn sure this bounce will occur when you remove the focus or switch to another control.
Finally, Remove animation code or leave this bounce animation (not bad).
:)

Not sure if you solved this, but for me it was because I had a piece of code in my UITextFieldDelegate calling layoutIfNeeded - which was causing all the auto-layout constraints to be recalculated.
I removed the layoutIfNeeded and seemed to have fixed the bouncing

Related

Setting UITextView Cursor Position

I have multiple uitextviews with their respective light gray placeholder text onLoad. Delegates are properly set.
My goal is, on any uitextview tap, to have the cursor selected to the start of the uitextview if the placeholder is present.
My problem is when I initially tap outside the placeholder text range (the blank space without text), the textviewdidchangeselection delegate doesn't even fire and the cursor begins at the end of the placeholder text. When I initially tap the placeholder text things work properly. When I initially tap the placeholder text and then try to tap outside the placeholder text range, things also work properly.
Any thoughts on why it is behaving the way it is? I tried both .selectedRange and .selectedTextRange to no avail.
func onLoad() {
textView1.text = placeHolderText1
textView1.textColor = UIColor.lightGray
textView2.text = placeHolderText2
textView2.textColor = UIColor.lightGray
textView1.textContainerInset.left = 4
textView1.layer.cornerRadius = 2
textView1.layer.masksToBounds = true
textView1.delegate = self
textView2.textContainerInset.left = 4
textView2.layer.cornerRadius = 2
textView2.layer.masksToBounds = true
textView2.delegate = self
}
func textViewDidChangeSelection(_ textView: UITextView) {
if self.view.window != nil {
if textView == textView1 {
if textView.textColor == UIColor.lightGray && textView.text == placeHolderText1 {
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
}
} else if textView == textView2 {
if textView.textColor == UIColor.lightGray && textView.text == placeHolderText2 {
textView.selectedRange = NSMakeRange(0, 0)
}
}
}
}
If I'm not wrong, your final goal is to have a working placeholder?
If yes, you can try this code:
//TextView place holder
extension UITextView{
func setPlaceholder() {
let placeholderLabel = UILabel()
placeholderLabel.text = "Enter your hashtags ..."
placeholderLabel.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
placeholderLabel.sizeToFit()
placeholderLabel.tag = 222
placeholderLabel.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !self.text.isEmpty
self.addSubview(placeholderLabel)
}
func checkPlaceholder() {
let placeholderLabel = self.viewWithTag(222) as! UILabel
placeholderLabel.isHidden = !self.text.isEmpty
}
}
with this code in your viewController class that has the UItextViewDelegate:
func textViewDidChange(_ textView: UITextView) {
textView1.checkPlaceholder()
textView2.checkPlaceholder()
}
and this code in your viewController class viewDidload:
textView1.setPlaceholder()
textView2.setPlaceholder()

How to create OTP verification screen and detect delete backward on multiple uitextfield is Swift

so i make this otp screen but i have some catch,
i make this otp screen with bunch of uitextfield and i make the logic of it, but i just cant delete on of the num in the textfield that i make
the textfield wont delete when i fill like the first 2 of my num, even i pressess backButton it wont work.....but it will work when i fill the whole num of textfield, in my case is six.
so i have to fill all six of the number and i can delete the number from the textfield, it wont work if only half fill in the textfield.
heres my code :
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if ((textField.text?.count)! < 1) && (string.count > 0) {
if textField == txtOTP1 {
txtOTP2.becomeFirstResponder()
}
if textField == txtOTP2 {
txtOTP3.becomeFirstResponder()
}
if textField == txtOTP3 {
txtOTP4.becomeFirstResponder()
}
if textField == txtOTP4 {
txtOTP5.becomeFirstResponder()
}
if textField == txtOTP5{
txtOTP6.becomeFirstResponder()
}
if textField == txtOTP6{
txtOTP6.resignFirstResponder()
}
textField.text = string
return false
}else if ((textField.text?.count)! >= 1) && (string.count == 0) {
if textField == txtOTP2{
txtOTP1.becomeFirstResponder()
}
if textField == txtOTP3{
txtOTP2.becomeFirstResponder()
}
if textField == txtOTP4{
txtOTP3.becomeFirstResponder()
}
if textField == txtOTP5{
txtOTP4.becomeFirstResponder()
}
if textField == txtOTP6{
txtOTP5.becomeFirstResponder()
}
if textField == txtOTP1{
txtOTP1.resignFirstResponder()
}
textField.text = ""
return false
}
else if (textField.text?.count)! >= 1 {
textField.text = string
return false
}
return true
}
thats the code i use to make the otp uitextField logic......please tell me i know theres something wrong with my logic, thanks.
i watch a tutorial to make this otp screen in this vid
https://www.youtube.com/watch?v=gZnBXh0TRO8
and according to the maker, he said that to fix this issue i just need to "set user interactions for textfield false and make first textfield first responder", i think i just did that but i maybe i did it wrong....
i really need to fix this guys, thanks.
Instead of fixing that code I prefer to create a custom text field that would inform when the deleteBackward key is pressed. So first subclass a UITextField:
import UIKit
class SingleDigitField: UITextField {
// create a boolean property to hold the deleteBackward info
var pressedDelete = false
// customize the text field as you wish
override func willMove(toSuperview newSuperview: UIView?) {
keyboardType = .numberPad
textAlignment = .center
backgroundColor = .blue
isSecureTextEntry = true
isUserInteractionEnabled = false
}
// hide cursor
override func caretRect(for position: UITextPosition) -> CGRect { .zero }
// hide selection
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] { [] }
// disable copy paste
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { false }
// override deleteBackward method, set the property value to true and send an action for editingChanged
override func deleteBackward() {
pressedDelete = true
sendActions(for: .editingChanged)
}
}
Now in your ViewCOntroller:
import UIKit
class ViewController: UIViewController {
// connect the textfields outlets
#IBOutlet weak var firstDigitField: SingleDigitField!
#IBOutlet weak var secondDigitField: SingleDigitField!
#IBOutlet weak var thirdDigitField: SingleDigitField!
#IBOutlet weak var fourthDigitField: SingleDigitField!
override func viewDidLoad() {
super.viewDidLoad()
// add a target for editing changed for each field
[firstDigitField,secondDigitField,thirdDigitField,fourthDigitField].forEach {
$0?.addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
// make the firsDigitField the first responder
firstDigitField.isUserInteractionEnabled = true
firstDigitField.becomeFirstResponder()
}
// here you control what happens to each change that occurs to the fields
#objc func editingChanged(_ textField: SingleDigitField) {
// check if the deleteBackwards key was pressed
if textField.pressedDelete {
// reset its state
textField.pressedDelete = false
// if the field has text empty its content
if textField.hasText {
textField.text = ""
} else {
// otherwise switch the field, resign the first responder and activate the previous field and empty its contents
switch textField {
case secondDigitField, thirdDigitField, fourthDigitField:
textField.resignFirstResponder()
textField.isUserInteractionEnabled = false
switch textField {
case secondDigitField:
firstDigitField.isUserInteractionEnabled = true
firstDigitField.becomeFirstResponder()
firstDigitField.text = ""
case thirdDigitField:
secondDigitField.isUserInteractionEnabled = true
secondDigitField.becomeFirstResponder()
secondDigitField.text = ""
case fourthDigitField:
thirdDigitField.isUserInteractionEnabled = true
thirdDigitField.becomeFirstResponder()
thirdDigitField.text = ""
default:
break
}
default: break
}
}
}
// make sure there is only one character and it is a number otherwise delete its contents
guard textField.text?.count == 1, textField.text?.last?.isWholeNumber == true else {
textField.text = ""
return
}
// switch the textField, resign the first responder and make the next field active
switch textField {
case firstDigitField, secondDigitField, thirdDigitField:
textField.resignFirstResponder()
textField.isUserInteractionEnabled = false
switch textField {
case firstDigitField:
secondDigitField.isUserInteractionEnabled = true
secondDigitField.becomeFirstResponder()
case secondDigitField:
thirdDigitField.isUserInteractionEnabled = true
thirdDigitField.becomeFirstResponder()
case thirdDigitField:
fourthDigitField.isUserInteractionEnabled = true
fourthDigitField.becomeFirstResponder()
default: break
}
case fourthDigitField:
fourthDigitField.resignFirstResponder()
default: break
}
}
}
Xcode 12 sample project

Swift: Disable bar button for whitespace/newline-only text in UITextView with placeholder text

I have a ViewController with a UITextView and a "Send" bar button item in the navigation bar which submits the text in the textView. Since the UITextView does not support placeholder text like the UITextField, I am handling it on my own with the following code which resides in the UITextViewDelegate method, shouldChangeTextInRange.
Note: The following code I wrote so far enables the Send button for whitespace/newline characters too. But this is what I need help with:
How can I disable the Send button when the textView contains only whitespace or newline characters but enable it otherwise while also setting/clearing the placeholder text appropriately?
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
// Combine the postTextView text and the replacement text to
// create the updated text string
let currentText : NSString = textView.text
let updatedText = currentText.stringByReplacingCharactersInRange(range, withString:text)
// If the updated textView text will be empty, disable the send button,
// set the placeholder text and color, and set the cursor to the beginning of the text view
if updatedText.isEmpty {
sendBarButton.enabled = false
textView.text = "Write something..."
textView.textColor = UIColor.lightGrayColor()
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
return false
}
// If the textView's placeholder is showing (i.e.: the textColor is light gray for placeholder text)
// and the length of the replacement string is greater than 0,
// clear the text view and set its color to black to prepare for the user to enter text
else if (textView.textColor == UIColor.lightGrayColor() && !(text.isEmpty)) {
sendBarButton.enabled = true
textView.text = nil
textView.textColor = UIColor.blackColor()
}
return true
}
UPDATE: I understand the following code may be used to trim/recognize whitespace and newline characters, but not sure how to apply it here in this case: stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
Thanks for your help.
I will do this way. I added a placeholder UILabel on UITextView which I will show or hide depending on the number of characters typed (0 or non zero).
Here is a Sample attached.
This is in Swift 3.x syntax. It will work on Xcode 8.x only.
I just made use of some more UITextView delegate methods as shown in my extension below
import UIKit
class ViewController: UIViewController {
var placeHolderLabel:UILabel!
#IBOutlet weak var myTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
placeHolderLabel = UILabel(frame: CGRect(x:5, y:5, width:240, height:22))
placeHolderLabel.text = "Send Placeholder"
//Set the font same as your textView font
placeHolderLabel.font = UIFont.systemFont(ofSize: 15.0)
//set the placeholder color
placeHolderLabel.textColor = UIColor.lightGray
myTextView.addSubview(placeHolderLabel)
//Initially disable the send button
sendButton.isEnabled = false
}
}
// MARK: - TextView Delegates
extension ViewController:UITextViewDelegate {
// Will handle the case for white spaces and will keep the send button disabled
func textViewDidChange(_ textView: UITextView) {
if(textView.text.characters.count != 0) {
if textView.text.characters.count > 1 {
return
}
textView.text = textView.text.trimmingCharacters(in: CharacterSet.whitespaces)
if textView.text.characters.count == 0 {
placeHolderLabel.isHidden = false
print("Disable Button")
sendButton.isEnabled = false
} else {
placeHolderLabel.isHidden = true
print("Enable Button")
sendButton.isEnabled = true
}
}
else {
placeHolderLabel.isHidden = false
print("Disable Button")
sendButton.isEnabled = false
}
}
// You can modify this, as I just made my keyboard to return whenever return key is pressed.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
if(textView.text.characters.count != 0) {
placeHolderLabel.isHidden = true
}
else {
placeHolderLabel.isHidden = false
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if(textView.text.characters.count != 0) {
placeHolderLabel.isHidden = true
}
else {
placeHolderLabel.isHidden = false
}
}
}
I was able to use part of #Rajan Maheshwari's answer to create a simple solution to my own problem (I think it can even be simplified further:
var placeholderLabel: UILabel!
func viewDidLoad() {
placeholderLabel = UILabel(frame: CGRect(x: 5, y: 5, width: 240, height: 18))
placeholderLabel.text = "Placeholder text..."
placeholderLabel.textColor = UIColor.lightGrayColor()
placeholderLabel.sizeToFit()
postTextView.addSubview(placeholderLabel)
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
let currentText: NSString = textView.text
let updatedText = currentText.stringByReplacingCharactersInRange(range, withString:text)
let trimmedText = updatedText.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
if !updatedText.isEmpty {
// Contains any text: hide placeholder
placeholderLabel.hidden = true
if trimmedText.isEmpty {
// Only whitespace and newline characters: disable button
sendBarButton.enabled = false
} else {
// No whitespace- and newline-only characters: enable button
sendBarButton.enabled = true
}
} else {
// No text at all: show placeholder, disable button
placeholderLabel.hidden = false
sendBarButton.enabled = false
}
return true
}
Credit: Thanks to #Rajan Maheshwari's help!

How to move cursor from one text field to another automatically in swift ios programmatically?

func textFieldDidBeginEditing(textField: UITextField) {
scrlView.setContentOffset(CGPointMake(0, textField.frame.origin.y-70), animated: true)
if(textField == firstDigit){
textField.becomeFirstResponder()
secondDigit.resignFirstResponder()
}
else if(textField == secondDigit){
textField.becomeFirstResponder()
thirdDigit.resignFirstResponder()
}
else if(textField == thirdDigit){
//textField.becomeFirstResponder()
fourthDigit.becomeFirstResponder()
}
I am using four textfields for OTP entry in which only one number can be entered at a time. After entering the number I need to move the cursor automatically to next textfield.
Set textField delegate and add target:
override func viewDidLoad() {
super.viewDidLoad()
first.delegate = self
second.delegate = self
third.delegate = self
fourth.delegate = self
first.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
second.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
third.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
fourth.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
}
Now when text changes change textField
func textFieldDidChange(textField: UITextField){
let text = textField.text
if text?.utf16.count >= 1{
switch textField{
case first:
second.becomeFirstResponder()
case second:
third.becomeFirstResponder()
case third:
fourth.becomeFirstResponder()
case fourth:
fourth.resignFirstResponder()
default:
break
}
}else{
}
}
And lastly when user start editing clear textField
extension ViewController: UITextFieldDelegate{
func textFieldDidBeginEditing(textField: UITextField) {
textField.text = ""
}
}
update Solution For Swift 5
In This solution, You will go to next Field. And When You Press Erase will come at previous text field.
Step 1: Set Selector for Text Field
override func viewDidLoad() {
super.viewDidLoad()
otpTextField1.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
otpTextField2.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
otpTextField3.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
otpTextField4.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
}
Step 2: Now We will handle move next text Field and Erase text Field.
#objc func textFieldDidChange(textField: UITextField){
let text = textField.text
if text?.count == 1 {
switch textField{
case otpTextField1:
otpTextField2.becomeFirstResponder()
case otpTextField2:
otpTextField3.becomeFirstResponder()
case otpTextField3:
otpTextField4.becomeFirstResponder()
case otpTextField4:
otpTextField4.resignFirstResponder()
default:
break
}
}
if text?.count == 0 {
switch textField{
case otpTextField1:
otpTextField1.becomeFirstResponder()
case otpTextField2:
otpTextField1.becomeFirstResponder()
case otpTextField3:
otpTextField2.becomeFirstResponder()
case otpTextField4:
otpTextField3.becomeFirstResponder()
default:
break
}
}
else{
}
}
Important Note: Don't Forget To set Delegate.
Swift 3 code to move the cursor from one field to another automatically in OTP(One Time Password) fields.
//Add all outlet in your code.
#IBOutlet weak var otpbox1: UITextField!
#IBOutlet weak var otpbox2: UITextField!
#IBOutlet weak var otpbox3: UITextField!
#IBOutlet weak var otpbox4: UITextField!
#IBOutlet weak var otpbox5: UITextField!
#IBOutlet weak var otpbox6: UITextField!
// Add the delegate in viewDidLoad
func viewDidLoad() {
super.viewDidLoad()
otpbox1?.delegate = self
otpbox2?.delegate = self
otpbox3?.delegate = self
otpbox4?.delegate = self
otpbox5?.delegate = self
otpbox6?.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range:NSRange, replacementString string: String) -> Bool {
// Range.length == 1 means,clicking backspace
if (range.length == 0){
if textField == otpbox1 {
otpbox2?.becomeFirstResponder()
}
if textField == otpbox2 {
otpbox3?.becomeFirstResponder()
}
if textField == otpbox3 {
otpbox4?.becomeFirstResponder()
}
if textField == otpbox4 {
otpbox5?.becomeFirstResponder()
}
if textField == otpbox5 {
otpbox6?.becomeFirstResponder()
}
if textField == otpbox6 {
otpbox6?.resignFirstResponder() /*After the otpbox6 is filled we capture the All the OTP textField and do the server call. If you want to capture the otpbox6 use string.*/
let otp = "\((otpbox1?.text)!)\((otpbox2?.text)!)\((otpbox3?.text)!)\((otpbox4?.text)!)\((otpbox5?.text)!)\(string)"
}
textField.text? = string
return false
}else if (range.length == 1) {
if textField == otpbox6 {
otpbox5?.becomeFirstResponder()
}
if textField == otpbox5 {
otpbox4?.becomeFirstResponder()
}
if textField == otpbox4 {
otpbox3?.becomeFirstResponder()
}
if textField == otpbox3 {
otpbox2?.becomeFirstResponder()
}
if textField == otpbox2 {
otpbox1?.becomeFirstResponder()
}
if textField == otpbox1 {
otpbox1?.resignFirstResponder()
}
textField.text? = ""
return false
}
return true
}
This is similar to how UberEats has their otp fields. You can just copy and paste this into a file and run it to see how it works. But don't forget to add the MyTextField class or it won't work.
If you want it to automatically to move to the next textfield after a number is entered and still be able to move backwards if the back button is pressed WHILE the textField is empty this will help you.
Like the very first thing I said, this is similar to how UberEats has their sms textFields working. You can't just randomly press a textField and select it. Using this you can only move forward and backwards. The ux is subjective but if Uber uses it the ux must be valid. I say it's similar because they also have a gray box covering the textField so I'm not sure what's going on behind it. This was the closest I could get.
First your going to have to subclass UITextField using this answer to detect when the backspace button is pressed. When the back button is pressed your going to erase everything inside that field AND the previous field then jump to the previous field.
Second your going to have to prevent the user from being able to select the left side of the cursor once a char is inside the textField using this answer. You override the method in the same subClass from the first step.
Third you need to detect which textField is currently active using this answer
Fourth your going to have to run some checks inside func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool using this YouTube tutorial. I added some things to his work.
I'm doing everything programmatically so you can copy and paste the entire code into a project and run it
First create a subClass of UITextField and name it MyTextField:
protocol MyTextFieldDelegate: class {
func textFieldDidDelete()
}
// 1. subclass UITextField and create protocol for it to know when the backButton is pressed
class MyTextField: UITextField {
weak var myDelegate: MyTextFieldDelegate? // make sure to declare this as weak to prevent a memory leak/retain cycle
override func deleteBackward() {
super.deleteBackward()
myDelegate?.textFieldDidDelete()
}
// when a char is inside the textField this keeps the cursor to the right of it. If the user can get on the left side of the char and press the backspace the current char won't get deleted
override func closestPosition(to point: CGPoint) -> UITextPosition? {
let beginning = self.beginningOfDocument
let end = self.position(from: beginning, offset: self.text?.count ?? 0)
return end
}
}
Second inside the class with the OTP textfields, set the class to use the UITextFieldDelegate and the MyTextFieldDelegate, then create a class property and name it activeTextField. When whichever textField becomes active inside textFieldDidBeginEditing you set the activeTextField to that. In viewDidLoad set all the textFields to use both delegates.
Make sure the First otpTextField is ENABLED and the second, third, and fourth otpTextFields are ALL initially DIASABLED
import UIKit
// 2. set the class to BOTH Delegates
class ViewController: UIViewController, UITextFieldDelegate, MyTextFieldDelegate {
let staticLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 17)
label.text = "Enter the SMS code sent to your phone"
return label
}()
// 3. make each textField of type MYTextField
let otpTextField1: MyTextField = {
let textField = MyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = UIFont.systemFont(ofSize: 25)
textField.autocorrectionType = .no
textField.keyboardType = .numberPad
textField.textAlignment = .center
// **important this is initially ENABLED
return textField
}()
let otpTextField2: MyTextField = {
let textField = MyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = UIFont.systemFont(ofSize: 25)
textField.autocorrectionType = .no
textField.keyboardType = .numberPad
textField.textAlignment = .center
textField.isEnabled = false // **important this is initially DISABLED
return textField
}()
let otpTextField3: MyTextField = {
let textField = MyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = UIFont.systemFont(ofSize: 25)
textField.autocorrectionType = .no
textField.keyboardType = .numberPad
textField.textAlignment = .center
textField.isEnabled = false // **important this is initially DISABLED
return textField
}()
let otpTextField4: MyTextField = {
let textField = MyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = UIFont.systemFont(ofSize: 25)
textField.autocorrectionType = .no
textField.keyboardType = .numberPad
textField.textAlignment = .center
textField.isEnabled = false // **important this is initially DISABLED
return textField
}()
// 4. create this property to know which textField is active. Set it in step 8 and use it in step 9
var activeTextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// 5. set the regular UItextField delegate to each textField
otpTextField1.delegate = self
otpTextField2.delegate = self
otpTextField3.delegate = self
otpTextField4.delegate = self
// 6. set the subClassed textField delegate to each textField
otpTextField1.myDelegate = self
otpTextField2.myDelegate = self
otpTextField3.myDelegate = self
otpTextField4.myDelegate = self
configureAnchors()
// 7. once the screen appears show the keyboard
otpTextField1.becomeFirstResponder()
}
// 8. when a textField is active set the activeTextField property to that textField
func textFieldDidBeginEditing(_ textField: UITextField) {
activeTextField = textField
}
// 9. when the backButton is pressed, the MyTextField delegate will get called. The activeTextField will let you know which textField the backButton was pressed in. Depending on the textField certain textFields will become enabled and disabled.
func textFieldDidDelete() {
if activeTextField == otpTextField1 {
print("backButton was pressed in otpTextField1")
// do nothing
}
if activeTextField == otpTextField2 {
print("backButton was pressed in otpTextField2")
otpTextField2.isEnabled = false
otpTextField1.isEnabled = true
otpTextField1.becomeFirstResponder()
otpTextField1.text = ""
}
if activeTextField == otpTextField3 {
print("backButton was pressed in otpTextField3")
otpTextField3.isEnabled = false
otpTextField2.isEnabled = true
otpTextField2.becomeFirstResponder()
otpTextField2.text = ""
}
if activeTextField == otpTextField4 {
print("backButton was pressed in otpTextField4")
otpTextField4.isEnabled = false
otpTextField3.isEnabled = true
otpTextField3.becomeFirstResponder()
otpTextField3.text = ""
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text {
// 10. when the user enters something in the first textField it will automatically adjust to the next textField and in the process do some disabling and enabling. This will proceed until the last textField
if (text.count < 1) && (string.count > 0) {
if textField == otpTextField1 {
otpTextField1.isEnabled = false
otpTextField2.isEnabled = true
otpTextField2.becomeFirstResponder()
}
if textField == otpTextField2 {
otpTextField2.isEnabled = false
otpTextField3.isEnabled = true
otpTextField3.becomeFirstResponder()
}
if textField == otpTextField3 {
otpTextField3.isEnabled = false
otpTextField4.isEnabled = true
otpTextField4.becomeFirstResponder()
}
if textField == otpTextField4 {
// do nothing or better yet do something now that you have all four digits for the sms code. Once the user lands on this textField then the sms code is complete
}
textField.text = string
return false
} // 11. if the user gets to the last textField and presses the back button everything above will get reversed
else if (text.count >= 1) && (string.count == 0) {
if textField == otpTextField2 {
otpTextField2.isEnabled = false
otpTextField1.isEnabled = true
otpTextField1.becomeFirstResponder()
otpTextField1.text = ""
}
if textField == otpTextField3 {
otpTextField3.isEnabled = false
otpTextField2.isEnabled = true
otpTextField2.becomeFirstResponder()
otpTextField2.text = ""
}
if textField == otpTextField4 {
otpTextField4.isEnabled = false
otpTextField3.isEnabled = true
otpTextField3.becomeFirstResponder()
otpTextField3.text = ""
}
if textField == otpTextField1 {
// do nothing
}
textField.text = ""
return false
} // 12. after pressing the backButton and moving forward again you will have to do what's in step 10 all over again
else if text.count >= 1 {
if textField == otpTextField1 {
otpTextField1.isEnabled = false
otpTextField2.isEnabled = true
otpTextField2.becomeFirstResponder()
}
if textField == otpTextField2 {
otpTextField2.isEnabled = false
otpTextField3.isEnabled = true
otpTextField3.becomeFirstResponder()
}
if textField == otpTextField3 {
otpTextField3.isEnabled = false
otpTextField4.isEnabled = true
otpTextField4.becomeFirstResponder()
}
if textField == otpTextField4 {
// do nothing or better yet do something now that you have all four digits for the sms code. Once the user lands on this textField then the sms code is complete
}
textField.text = string
return false
}
}
return true
}
//**Optional** For a quick setup use this below. Here is how to add a gray line to the textFields and here are the anchors:
// if your app supports portrait and horizontal your going to have to make some adjustments to this every time the phone rotates
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
addBottomLayerTo(textField: otpTextField1)
addBottomLayerTo(textField: otpTextField2)
addBottomLayerTo(textField: otpTextField3)
addBottomLayerTo(textField: otpTextField4)
}
// this adds a lightGray line at the bottom of the textField
func addBottomLayerTo(textField: UITextField) {
let layer = CALayer()
layer.backgroundColor = UIColor.lightGray.cgColor
layer.frame = CGRect(x: 0, y: textField.frame.height - 2, width: textField.frame.width, height: 2)
textField.layer.addSublayer(layer)
}
func configureAnchors() {
view.addSubview(staticLabel)
view.addSubview(otpTextField1)
view.addSubview(otpTextField2)
view.addSubview(otpTextField3)
view.addSubview(otpTextField4)
let width = view.frame.width / 5
staticLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15).isActive = true
staticLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
staticLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
// textField 1
otpTextField1.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
otpTextField1.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
otpTextField1.widthAnchor.constraint(equalToConstant: width).isActive = true
otpTextField1.heightAnchor.constraint(equalToConstant: width).isActive = true
// textField 2
otpTextField2.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
otpTextField2.leadingAnchor.constraint(equalTo: otpTextField1.trailingAnchor, constant: 10).isActive = true
otpTextField2.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
otpTextField2.heightAnchor.constraint(equalToConstant: width).isActive = true
// textField 3
otpTextField3.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
otpTextField3.leadingAnchor.constraint(equalTo: otpTextField2.trailingAnchor, constant: 10).isActive = true
otpTextField3.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
otpTextField3.heightAnchor.constraint(equalToConstant: width).isActive = true
// textField 4
otpTextField4.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
otpTextField4.leadingAnchor.constraint(equalTo: otpTextField3.trailingAnchor, constant: 10).isActive = true
otpTextField4.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
otpTextField4.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
otpTextField4.heightAnchor.constraint(equalToConstant: width).isActive = true
}
}
This is separate from the answer above but if you need to add multiple characters to each otpTextField then follow this answer.
Firstly we'll need to set the tag for the UITextField;
func textFieldShouldReturnSingle(_ textField: UITextField , newString : String)
{
let nextTag: Int = textField.tag + 1
let nextResponder: UIResponder? = textField.superview?.superview?.viewWithTag(nextTag)
textField.text = newString
if let nextR = nextResponder
{
// Found next responder, so set it.
nextR.becomeFirstResponder()
}
else
{
// Not found, so remove keyboard.
textField.resignFirstResponder()
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newString = ((textField.text)! as NSString).replacingCharacters(in: range, with: string)
let newLength = newString.characters.count
if newLength == 1 {
textFieldShouldReturnSingle(textField , newString : newString)
return false
}
return true
}
Note: The UITextField takes only one character in number format, which is in OTP format.
Objective c and Swift 4.2 to move the cursor from one field to another automatically in OTP(One Time Password) fields
Here i am taking one view controller
]1
Then give the Tag values for each TextFiled.Those related reference images are shown below
Enter tag value for first textfiled --> 1,2ndTextfiled ---->2,3rd TextFiled --->3 4rth TextFiled---->4
Then assign Textfiled Delegates and write below code and see the magic
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString
*)string
{
if ((textField.text.length < 1) && (string.length > 0))
{
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview
viewWithTag:nextTag];
if (! nextResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (nextResponder)
[nextResponder becomeFirstResponder];
return NO;
}else if ((textField.text.length >= 1) && (string.length == 0)){
// on deleteing value from Textfield
NSInteger prevTag = textField.tag - 1;
// Try to find prev responder
UIResponder* prevResponder = [textField.superview
viewWithTag:prevTag];
if (! prevResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (prevResponder)
// Found next responder, so set it.
[prevResponder becomeFirstResponder];
return NO;
}
return YES;
}
swift4.2 version code
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text!.count < 1 && string.count > 0 {
let tag = textField.tag + 1;
let nextResponder = textField.superview?.viewWithTag(tag)
if (nextResponder != nil){
textField.resignFirstResponder()
}
textField.text = string;
if (nextResponder != nil){
nextResponder?.becomeFirstResponder()
}
return false;
}else if (textField.text?.count)! >= 1 && string.count == 0 {
let prevTag = textField.tag - 1
let prevResponser = textField.superview?.viewWithTag(prevTag)
if (prevResponser != nil){
textField.resignFirstResponder()
}
textField.text = string
if (prevResponser != nil){
prevResponser?.becomeFirstResponder()
}
return false
}
return true;
}
Here I was take 4 TextField
#IBOutlet var txtOtp: [BottomBorderTextField]!
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
defer{
if !string.isEmpty {
textField.text = string
textField.resignFirstResponder()
if let index = self.txtOtp.index(where:{$0 === textField}) {
if index < 3 {
self.txtOtp[index + 1].becomeFirstResponder()
}
}
}
}
return true
}
**call from UITextfieldDelegate function and make next text field the first responder and no need to add target and remember to set delegates of all text fields in viewDidLoad **
extension ViewController : UITextFieldDelegate {
func textFieldShouldReturn(textField: UITextField) -> Bool {
nextTextFieldToFirstResponder(textField)
return true;
}
func nextTextFieldToFirstResponder(textField: UITextField) {
if textField == emailTextField
{
self.firstNameTextField.becomeFirstResponder()
}
else if textField == firstNameTextField {
self.lastNameTextField.becomeFirstResponder()
}
else if textField == lastNameTextField {
self.passwordTextField.becomeFirstResponder()
}
else if textField == passwordTextField {
self.confirmPassTextField.becomeFirstResponder()
}
else if textField == confirmPassTextField {
self.confirmPassTextField.resignFirstResponder()
}
}
I have tried many codes and finally this worked for me in Swift 3.0 Latest [March 2017]
The "ViewController" class should inherited the "UITextFieldDelegate" for making this code working.
class ViewController: UIViewController,UITextFieldDelegate
Add the Text field with the Proper Tag nuber and this tag number is used to take the control to appropriate text field based on incremental tag number assigned to it.
override func viewDidLoad() {
userNameTextField.delegate = self
userNameTextField.tag = 0
userNameTextField.returnKeyType = UIReturnKeyType.next
passwordTextField.delegate = self
passwordTextField.tag = 1
passwordTextField.returnKeyType = UIReturnKeyType.go
}
In the above code, the "returnKeyType = UIReturnKeyType.next" where will make the Key pad return key to display as "Next" you also have other options as "Join/Go" etc, based on your application change the values.
This "textFieldShouldReturn" is a method of UITextFieldDelegate controlled and here we have next field selection based on the Tag value incrementation
func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
return true;
}
return false
}
Use textFieldShouldBeginEditing method
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
scrlView.setContentOffset(CGPointMake(0, textField.frame.origin.y-70),
animated:true)
if(textField == firstDigit){
textField.becomeFirstResponder()
secondDigit.resignFirstResponder()
}
else if(textField == secondDigit){
textField.becomeFirstResponder()
thirdDigit.resignFirstResponder()
}
else if(textField == thirdDigit){
//textField.becomeFirstResponder()
fourthDigit.becomeFirstResponder()
}
return true;
}
//MARK:- IBOutlets
#IBOutlet weak var tfFirstDigit: UITextField!
#IBOutlet weak var tfSecondDigit: UITextField!
#IBOutlet weak var tfThirdDigit: UITextField!
#IBOutlet weak var tfFourthDigit: UITextField!
//MARK:- view Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
tfFirstDigit.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
tfSecondDigit.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
tfThirdDigit.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
tfFourthDigit.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
}
//MARK:- Text Field Delegate methods
#objc func textFieldDidChange(textField: UITextField){
let text = textField.text
if (text?.utf16.count)! >= 1{
switch textField{
case tfFirstDigit:
tfSecondDigit.becomeFirstResponder()
case tfSecondDigit:
tfThirdDigit.becomeFirstResponder()
case tfThirdDigit:
tfFourthDigit.becomeFirstResponder()
case tfFourthDigit:
tfFourthDigit.resignFirstResponder()
default:
break
}
}else{
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.text = ""
}
let do something different using IQKeyboardManager.It work like charm. Do not forget set delegate for every text field.
//MARK:- TextField delegate methods
#objc func textFieldDidChange(textField: UITextField){
if textField.text!.count == 1{
if IQKeyboardManager.shared().canGoNext{
IQKeyboardManager.shared().goNext()
}
}else{
if IQKeyboardManager.shared().canGoPrevious{
IQKeyboardManager.shared().goPrevious()
}
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == " "{
return false
}else if string.isEmpty{
return true
}else if textField.text!.count == 1{
textField.text = string
if IQKeyboardManager.shared().canGoNext{
IQKeyboardManager.shared().goNext()
}
return false
}
return true
}
for swift 3
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if ((textField.text?.characters.count)! < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
textField.text = string;
if (nextResponder == nil){
textField.resignFirstResponder()
}
nextResponder?.becomeFirstResponder();
return false;
}
else if ((textField.text?.characters.count)! >= 1 && string.characters.count == 0){
// on deleting value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
return true;
}

How to listen for UIReturnKeyType.Next with Swift iOS

When the user presses next on the keypad I want to move from the current UITextField to the next UITextField, the UIReturnKeyType is set to UIReturnKeyType.Next
Here is how I have the UITextField set up.
username.returnKeyType = UIReturnKeyType.Next
username.textColor = UIColor.whiteColor()
username.placeholder = "Username"
username.borderStyle = UITextBorderStyle.RoundedRect
uUsername.textAlignment = NSTextAlignment.Center
username.backgroundColor = UIColor.lightGrayColor()
username.font = UIFont(name: "Avenir Next", size: 14)
username.autocapitalizationType = UITextAutocapitalizationType.None
username.autocorrectionType = UITextAutocorrectionType.No
You have to configure the UITextFieldDelegate method textFieldShouldReturn:. From within this method, you can check which text field is returning and reassign first responder status accordingly. Of course this mean you'll have to assign your class as the delegate of the text fields.
Example:
class MyClass: UITextFieldDelegate {
func setup() { // meaningless method name
textField1.delegate = self
textField2.delegate = self
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
if (textField === textField1) {
textField2.becomeFirstResponder()
} else if (textField === textField2) {
textField2.resignFirstResponder()
} else {
// etc
}
return true
}
}
Follow the steps given below:
1) You will be needing to set the tags in the incremental sequence of the textfields in xib/Storyboard or in code.
2) Set the delegate for each of the textfields.
3) Then paste the following code in your view controller to have the desired effect:
func textFieldShouldReturn(textField: UITextField) -> Bool
{
let nextTag: Int = textField.tag + 1
let nextResponder: UIResponder? = textField.superview?.superview?.viewWithTag(nextTag)
if let nextR = nextResponder
{
// Found next responder, so set it.
nextR.becomeFirstResponder()
}
else
{
// Not found, so remove keyboard.
textField.resignFirstResponder()
}
return false
}
Hope this helps!!
If you have two textfield :
- textField1
- textField2
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField1.resignFirstResponder()
textField2.becomeFirstResponder()
return true
}
class ViewController: UIViewController,UITextFieldDelegate //set delegate to class
#IBOutlet var txtValue: UITextField //create a textfield variable
override func viewDidLoad() {
super.viewDidLoad()
txtValue.delegate = self //set delegate to textfile
}
func textFieldShouldReturn(textField: UITextField!) -> Bool { //delegate method
return true
}

Resources