Digit input in a text field - swift

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
}

Related

UITextLabel updating in different order during backspace of UITextField than other entries.

Okay so I have a UITextLabel that is being updated to whatever and whenever UITextField is changed.
So I have the following method.
func textField(_ textField: UITextField, shouldChangeCharactersInrange: NSRange, replacementString string: String) -> Bool {
payment_amount_label.text = payment_amount_tf.text!
return true
}
and I have the textfield set up as a currency field. here is the code for that.
class CurrencyField: UITextField {
var string: String { return text ?? "" }
var decimal: Decimal {
return string.digits.decimal /
Decimal(pow(10, Double(Formatter.currency.maximumFractionDigits)))
}
var decimalNumber: NSDecimalNumber { return decimal.number }
var doubleValue: Double { return decimalNumber.doubleValue }
var integerValue: Int { return decimalNumber.intValue }
let maximum: Decimal = 9_999.99
private var lastValue: String?
override func willMove(toSuperview newSuperview: UIView?) {
// you can make it a fixed locale currency if needed
// Formatter.currency.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
keyboardType = .numberPad
textAlignment = .right
editingChanged()
}
override func deleteBackward() {
text = string.digits.dropLast().string
print("inside method delete" + text!)
editingChanged()
//backspace not working for editingchanged. not sure why.
}
#objc func editingChanged(_ textField: UITextField? = nil) {
guard decimal <= maximum else {
text = lastValue
return
}
text = Formatter.currency.string(for: decimal)
lastValue = text
print("inside method editing" + text! + "last value is " + lastValue!)
return
}
}
extension NumberFormatter {
convenience init(numberStyle: Style) {
self.init()
self.numberStyle = numberStyle
}
}
extension Formatter {
static let currency = NumberFormatter(numberStyle: .currency)
}
extension String {
var digits: [UInt8] {
return map(String.init).compactMap(UInt8.init)
}
}
extension Collection where Iterator.Element == UInt8 {
var string: String { return map(String.init).joined() }
var decimal: Decimal { return Decimal(string: string) ?? 0 }
}
extension Decimal {
var number: NSDecimalNumber { return NSDecimalNumber(decimal: self) }
}
When I enter in numbers into the keypad everything updates correctly. However, when I hit backspace the textfield keeps the old value. I put in some print statements to see what was happening and when backspace is hit the UITextLabel is being updated before the delete happens inside the CurrencyField code. When a number is pressed the UITextLabel is updated AFTER the the CurrencyField code. I have no idea how to fix this issue any help is appreciated.
Try below:
Add a custom method for change in value to UITextField same as UITextView.
In viewDidLoad:
payment_amount_tf.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
Declare textFieldDidChange as:
#objc func textFieldDidChange(_ sender: UITextField) {
payment_amount_label.text = payment_amount_tf.text
}

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)
}
}

Switching to next UITextField when character limit is reached

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()
}
}

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
}

Avoid copying code for a lot of buttons

Edit on my question:
Grimxn, I made a subclass and can see it works, because of the borderWidth and color. But I still have a couple of questions on how to add my function:
Should I code "func textField(textField: UITextField" or "func textField(textField: MyCustomTextField" ?
What should I do with "if textField == numberField01 {" ?
How do I 'call this' from the ViewController code ?
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var numberField01: UITextField!
#IBOutlet weak var numberField02: MyCustomTextField!
override func viewDidLoad() {
super.viewDidLoad()
numberField01.delegate = self
numberField01.keyboardType = UIKeyboardType.NumberPad
numberField02.delegate = self
numberField02.keyboardType = UIKeyboardType.NumberPad
}
class MyCustomTextField: UITextField {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderColor = UIColor.redColor().CGColor
self.layer.borderWidth = 1.5
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 == numberField01 {
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
}
}
}
Original question:
The following code is working fine for one textfield (numberField01). It makes sure the input is decimal only, places a decimal point, and prevents a user to paste in a non decimal string. But I have a lot more buttons... (numberField02 and up). How can I handle more buttons, without just copying my code for each button?
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var numberField01: UITextField!
#IBOutlet weak var numberField02: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
numberField01.delegate = self
numberField01.keyboardType = UIKeyboardType.NumberPad
numberField02.delegate = self
numberField02.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(numberField01.text) > 0 {
var numberString = numberField01.text
numberString = numberString.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
var numberFromString = Double(numberString.toInt()!) / 100
numberField01.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 == numberField01 {
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
}
}
So something similar to the following:
Button GetButtonCommonFeatures(Button myButton)
{
Write common code here....
e.g. myButton.delegate = self;...
return myButton;
}
Then call your method for each button. Lets take numberField01 for example. You will include the code in the method that applies to every button.
numberField01 = GetButtonCommonFeatures(numberField01);
Hope this helps