Switching to next UITextField when character limit is reached - swift

I have three text fields to enter in a phone number. I am trying to set the character limit for each textfield to three characters and once this is reached switch to a new textfield.
I saw online to use this code to limit the characters:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentCharacterCount = textField.text?.characters.count ?? 0
if (range.length + range.location > currentCharacterCount){
return false
}
let newLength = currentCharacterCount + string.characters.count - range.length
return newLength <= 25
}
and to use this to switch to typing on a new textfield:
.didbecomefirstresponder()
but I am not sure how to limit a text field to 3 characters and then switch to the next field.

This is my code, how I solve this:
The three textfields are:
#IBOutlet var txtField1: UITextField!
#IBOutlet var txtField2: UITextField!
#IBOutlet var txtField3: UITextField!
import UITextFieldDelegate in to your class and set the delegate to self for all the textfields.
And use this method to change focus.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentCharacterCount = ((textField.text?.characters.count)! + string.characters.count) - 1
switch (textField, currentCharacterCount) {
case (self.txtField1, 3):
self.txtField2.becomeFirstResponder()
case (self.txtField2, 7):
self.txtField3.becomeFirstResponder()
default:
break
}
return true
}
Here I set character count 3 for the first textfield, and 7 for second textfield.

You could use the delegate method for UITextField shouldChangeCharactersInRange. You'd have to do a little setup work though. Here's an example that creates 3 textFields, conforms to the UITextFieldDelegate, and does something similar to what you're describing.
class ViewController: UIViewController, UITextFieldDelegate {
var max = 3
var fields:[UITextField] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// This is just an example to layout 3 text fields.
for i in 0 ..< 3 {
let field = UITextField()
view.addSubview(field)
field.delegate = self
field.borderStyle = .roundedRect
field.font = UIFont(name: "AvenirNext-Regular", size: 15.0)
field.textColor = UIColor.black
field.frame = CGRect(x: view.bounds.width/2 - 100, y: 100 + (150*CGFloat(i)), width: 200, height: 50)
fields.append(field)
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Make sure the field has non-nil text
if let text = textField.text {
// Check if the text length has reached max limit
if text.characters.count > (max-1) {
// Get index of field from fields array
if let index = fields.index(of: textField) {
// Make sure it's not the last field in the array else return false
if index < fields.count-1 {
// Make the next field in the array become first responder if it's text is non-nil and it's text's character count is less than desired max
if fields[index+1].text != nil && fields[index+1].text!.characters.count < (max-1) {
fields[index+1].becomeFirstResponder()
} else {
return false
}
} else {
return false
}
}
}
}
return true
}
}

I find Pierce's answers too complex and anas.p's answer doesn't work for me like it's supposed to. My Swift 5 solution:
override func viewDidLoad() {
textField1.addTarget(self, action: #selector(textFieldDidChange1), for:.editingChanged)
textField2.addTarget(self, action: #selector(textFieldDidChange2), for:.editingChanged)
}
#objc func textFieldDidChange1() {
// After 2 characters are typed, immediately jump to textField2
if (textField1.text?.count == 2) {
self.textField2.becomeFirstResponder()
}
}
#objc func textFieldDidChange2() {
if (textField2.text?.count == 2) {
self.textField3.becomeFirstResponder()
}
}

Related

Want to change UITextview height while typing

I want that if user add text and it add new line it should increase height and if user delete soometext and some line than it should decrease its height
For this I'have tried below solution
func textViewDidChange(_ textView: UITextView) {
self.updateCharacterCount()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4),
execute: {
let pos = textView.endOfDocument
self.referenceOfClass?.textOfTextViewForTitle =
self.title_Name.text
let currentRect = textView.caretRect(for: pos ??
UITextPosition.init())
if (currentRect.origin.y == -1 || currentRect.origin.y ==
CGFloat.infinity){
if let val = self.indexPath{
self.referenceOfClass?.previousRectforTitle = currentRect
self.referenceOfClass?.inq_Tableview.reloadRows(at:
[val], with: .automatic)
}
}
else if currentRect.origin.y <
self.referenceOfClass?.previousRectforTitle.origin.y ?? 0.0 {
if let val = self.indexPath{
self.referenceOfClass?.previousRectforTitle = currentRect
self.referenceOfClass?.inq_Tableview.reloadRows(at:
[val], with: .automatic)
}
}
})
}
but it is not working in this textView.caretRect(for: pos ??
UITextPosition.init()) always return same value (0,-1)
So how can I solve this?
I am using xcode10.1 and this textview is in tableviewcell
You can try this,
Take Outlet of hight constraint and code below
#IBOutlet weak var txtMessageHeightConstraint: NSLayoutConstraint!
extension GigMessageSocketViewController:UITextViewDelegate
{
func textViewDidChange(_ textView: UITextView)
{
isClickOnSendMessage = true
let contentSize = txtMessage.sizeThatFits(CGSize(width: txtMessage.bounds.size.width, height: CGFloat(Float.greatestFiniteMagnitude)))
if textView.contentSize.height < 100.0
{
txtMessageHeightConstraint.constant = CGFloat(ceilf(Float(contentSize.height)))
textView.layoutIfNeeded()
textView.updateConstraints()
textView.scrollRangeToVisible(textView.selectedRange)
}
}
}
My first thought on how to solve this issue would be to create a custom UITextfield that it would be a UIView with content mode scaleToFill that inside a UILabel and you would make every component of the UITextfield clear
self.textColor = UIColor.clear
self.tintColor = UIColor.clear
self.borderStyle = UITextBorderStyle.none
self.placeholder=""
and on the function
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {}
You would update the UILabel text so the scaling would be automated because of the contraints.

how to add a string after each comma in UITextfield

Im lookin a way to add # at the beginig of each word written in uiTextField with swift, i tryes to check using this code
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text!.first != "#" {
print(textField.text!.first)
print(textField.text!)
textField.text = ""
}
}
but the firsrt character is nil when the input on keyboard is # so what should be the way to achive this having all the words begins with # and separated by ,
You can make it easier checking the text after editing changed control event and clean your string when the user types a space after each word. You can subclass your UITextField and it should look something like this:
class TagsField: UITextField, UITextFieldDelegate {
override func didMoveToSuperview() {
delegate = self
keyboardType = .alphabet
autocapitalizationType = .none
autocorrectionType = .no
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
prepareString()
if text!.hasSuffix(", #") { text!.removeLast(3) } // clean the residue on end
resignFirstResponder()
return false
}
func prepareString() {
text = text!.components(separatedBy: CharacterSet.letters.inverted) // filtering non letters and grouping words
.filter{!$0.isEmpty} // filtering empty components
.map{ "#" + $0 + ", " } // add prefix and sufix to each word and append # to the end of the string
.string + "#"
}
override func deleteBackward() {
let _ = text!.popLast() // manually pops the last character when deliting
}
#objc func editingChanged(_ textField: UITextField) {
if text!.last == " " {
prepareString()
} else if !text!.hasPrefix("#") { // check if the first word being typed has the # prefix and add it if needed.
text!.insert("#", at: text!.startIndex)
}
}
}
extension Collection where Element: StringProtocol {
var string: String {
return String(joined())
}
}

How can I get a number input from a TextField?

I will get right to the question.
var a = 0
var b = 20
I want a user to input number into TextField and I could save that number into variable A. Then I want to do an if statement where
if a == b {
//code
}
What I am having trouble is getting that number input from the textfield.
You can check the input in the textfield for get the number
Swift4
let text = textField.text ?? ""
guard let number = Int(text) else {
print("Must be input the number")
return
}
// Do your task with number
a = number
Try this class Functions
class SourceVC: UIViewController
{
#IBOutlet weak var sampleTF: UITextField!{
didSet{
sampleTF.delegate = self
}
}
/// TF value
var a : Int = 0
var b : Int = 20
override func viewDidLoad() {
super.viewDidLoad()
}
/// Assuming this button action to get Value from TF
#IBAction func naviagteToDestination(_ sender: Any)
{
a = Int((sampleTF.text?.trimmingCharacters(in: .whitespaces))!)!
if a == b {
print("Execute")
}
else{
print("Dont Execute")
}
}
}
extension SourceVC : UITextFieldDelegate
{
/// This will Limit TF to accept only Numbers
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
let allowedCharacters = CharacterSet.decimalDigits
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}
}

shouldChangeCharactersInRange multiple text fields gets frozen

Updated code bellow with solution.
This works with as many fields you want.
It also fixes the textfield frozen issue when the method returns false.
The line bellow is what guides the method to return true after returns false.
newString = currentString.stringByReplacingCharactersInRange(range, withString: string)
On view did load (this will add an identifier to each field so you can identify within the method what field is being used - it is an Int())
emailAddressField.tag = 1
userPasswordField.tag = 2
On delegate method
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
var maxLength = Int()
var newString = NSString()
var currentString = NSString()
println("MY TAG \(textField.tag)")
switch textField.tag {
case 1:
println("CASE 1 \(range)")
maxLength = 16
currentString = emailAddressField.text
newString = currentString.stringByReplacingCharactersInRange(range, withString: string)
case 2:
println("CASE 2 \(range)")
maxLength = 8
currentString = userPasswordField.text
newString = currentString.stringByReplacingCharactersInRange(range, withString: string)
default:
println("Didn't detect any field")
}
return newString.length <= maxLength
}
The issue is that the delegate method shouldChangeCharactersInRange is used for both text fields and in your implementation you're returning false as soon as one of the text fields reaches its limit which ultimately makes both text fields deny further input. To resolve this you need to check the method's textField parameter to identify which text field the method is called for.
One of the possible ways to do this is to set up tags on your two text fields, for instance in viewDidLoad,
override func viewDidLoad() {
...
emailAddressField.tag = EMAIL_ADDRESS_TEXTFIELD // e.g. 0
userPasswordField.tag = USER_PASSWORD_TEXTFIELD // e.g. 1
}
and then act upon the tag of the text field supplied to the delegate method
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let newString = textField.text!.stringByReplacingCharactersInRange(range, withString: string)
if textField.tag == EMAIL_ADDRESS_TEXTFIELD && count(newString) + 1 == 30 {
return false
}
if textField.tag == USER_PASSWORD_TEXTFIELD && count(newString) + 1 == 11 {
return false
}
return true
}

Digit input in a text field

Additional question:
Still need some help with my code. The textfield is 'measuredValue' and I plan to have 30 different texfields (measuredValue1...30). When I type '923' the text will be set to '9.23' right away. Then I want to add '4'... for '92.34' but that doesn't work. Thanks for helping out.
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool {
if count(string) == 0 { return true }
var measuredValue = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
switch textField {
case digitsOnlyTextField:
if count(measuredValue) == 3 || count(measuredValue) == 4 {
let stringNumeric = Double(measuredValue.toInt()!) / 100
measuredValue = String(format:"%.2f", stringNumeric)
digitsOnlyTextField.text = measuredValue
}
return measuredValue.containsOnlyCharactersIn("0123456789") && count(measuredValue) <= 4
default:
return true
}
}
Original question:
I would like to validate my text fields to get the right input for my app. Input needs to be formatted like '9.90' or '15.34'. So always 3 or 4 digits, and always 2 decimals.
I would like to use 'numberpad keyboard' (just 0...9, no point) and add the decimal point after the user exits the field. So the user input is 990 or 1534, and then in the text field it will become 9.90 or 15.34 automatically.
I tried searching for examples first, but didn't find what I was looking for.
Any help appreciated.
Jan
You have to implement the UITextField delegate method
func textFieldDidEndEditing(textField: UITextField) {
//Logic goes here....
var textString = textField.text as! String
let textLength = countElements(textString)
if textLength >= 3 && textLength <= 4 {
let stringNumeric = Double(textString.toInt()!) / 100
let texts = String(format:"%.2f", stringNumeric)
textField.text = texts
}
}
Your class should confirm to the protocol UITextFieldDelegate
this is the 'final' code I used (but I'm open for improvements!). A few remarks:
when a user clicks the background (and leaves the textfield) the
decimal point gets set.
when a user copies & pastes text from another app, this code handles that.
when a user goes back in the textfield for editing, the decimal point gets set again.
Deleting a value (to an empty textfield) is handled correctly.
So I'm pleased. Thanks for the help.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var numberField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
numberField.delegate = self
numberField.keyboardType = UIKeyboardType.NumberPad
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Tap background to add decimal point and defocus keyboard
#IBAction func userTappedBackground(sender: AnyObject) {
for view in self.view.subviews as! [UIView] {
if let textField = view as? UITextField {
if count(numberField.text) > 0 {
var numberString = numberField.text
numberString = numberString.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
var numberFromString = Double(numberString.toInt()!) / 100
numberField.text = String(format:"%.2f", numberFromString)
}
textField.resignFirstResponder()
}
}
}
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool {
var result = true
var prospectiveText = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
prospectiveText = prospectiveText.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
if textField == numberField {
if count(string)>0 {
let disallowedCharacterSet = NSCharacterSet(charactersInString: "0123456789").invertedSet
let replacementStringIsLegal = string.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
let resultingStringLengthIsLegal = count(prospectiveText) <= 4
let scanner = NSScanner(string: prospectiveText)
let resultingTextIsNumeric = scanner.scanDecimal(nil) && scanner.atEnd
result = replacementStringIsLegal && resultingStringLengthIsLegal && resultingTextIsNumeric
}
}
return result
}