I am developing a Point Of Sales app.
So I would like to
Let's say User input 100000 but I want it to automatically show up 100,000. and 1000000 become 1,000,000
The second problem is that, I don't want user to be able to input . themselves.
Third problem is that since this is money, we can't let user to enter 0 in the beginning.
Any ideas?
So far I have managed to restrict the input to decimal digits only
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let numberSet: NSCharacterSet = NSCharacterSet.decimalDigitCharacterSet().invertedSet;
return string.stringByTrimmingCharactersInSet(numberSet).length > 0 || string == "";
}
Thank you very much
P.S.: I do not need any decimal places, also we need to take into account when the user change the cursor position when hitting backspace
Xcode 9 • Swift 4
import UIKit
class IntegerField: UITextField {
var lastValue = 0
let maxValue = 1_000_000_000
var amount: Int {
if let newValue = Int(string.digits), newValue < maxValue {
lastValue = newValue
} else if !hasText {
lastValue = 0
}
return lastValue
}
override func didMoveToSuperview() {
textAlignment = .right
keyboardType = .numberPad
text = Formatter.decimal.string(for: amount)
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
#objc func editingChanged(_ textField: UITextField) {
text = Formatter.decimal.string(for: amount)
}
}
extension NumberFormatter {
convenience init(numberStyle: Style) {
self.init()
self.numberStyle = numberStyle
}
}
struct Formatter {
static let decimal = NumberFormatter(numberStyle: .decimal)
}
extension UITextField {
var string: String { return text ?? "" }
}
extension String {
private static var digitsPattern = UnicodeScalar("0")..."9"
var digits: String {
return unicodeScalars.filter { String.digitsPattern ~= $0 }.string
}
}
extension Sequence where Iterator.Element == UnicodeScalar {
var string: String { return String(String.UnicodeScalarView(self)) }
}
Simple thing i came up with fully tested on Swift 2.0
you can use the any of the textField delegate see which one suites you
let price = Int(textField.text!)
let _curFormatter : NSNumberFormatter = NSNumberFormatter()
_curFormatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
_curFormatter.currencyCode = "INR"
_curFormatter.maximumFractionDigits = 0
let total = _curFormatter.stringFromNumber(price!)
textField.text = total
Related
I used a variable to store the text being entered and modifying it by appending the remaining suffix of "mm/dd/yyyy". I am getting the functionality, but if I try to update the cursor to the right position, its creating a problem.
I used the textfield.selectedTextRange to move the cursor from EOF to position I need. But, it is replacing the text entered with last "y" from "mm/dd/yyyy". So If I enter "12" the text changes from mm/dd/yyyy| to "yy|/mm/yyyy" instead of "12|/dd/yyy"
Am I doing it the wrong way?
let dobPlaceholderText = "mm/dd/yyyy"
var enteredDOBText = ""
#objc func textFieldDidChange(_ textField: UITextField){
enteredDOBText.append(textField.text.last ?? Character(""))
let modifiedText = enteredDOBText + dobPlaceholderText.suffix(dobPlaceholderText.count - enteredDOBText.count)
textField.text = modifiedText
setCursorPosition(input: dob.textField, position: enteredDOBText.count)
}
private func setCursorPosition(input: UITextField, position: Int){
let position = input.position(from: input.beginningOfDocument, offset: position)!
input.selectedTextRange = input.textRange(from: position, to: position)
}
The suggestion of matt and aheze in the comments to use textField(_:shouldChangeCharactersIn:replacementString:) is very promising in such use cases.
The procedure could look something like this:
determine the resulting text as it would look like after the user input and filter out all non-numeric characters
apply the pattern as defined in your dobPlaceholderText
set the result in the UIText field
determine and set new cursor position
In source code it could look like this:
let dobPlaceholderText = "mm/dd/yyyy"
let delims = "/"
let validInput = "0123456789"
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
let filteredText = filtered(textField.text, range: range, replacement: string)
let newText = applyPattern(filteredText, pattern: Array(dobPlaceholderText))
textField.text = newText
let newPosition = newCursorPosition(range: range, replacement: string, newText: newText)
setCursorPosition(input: textField, position: newPosition)
return false
}
The actual implementation of the three methods filtered, applyPattern and newCursorPosition depends on the exact detail behavior you want to achieve. This is just a sample implementation, use something that reflects your requirements.
private func filtered(_ text: String?,range:NSRange, replacement: String) -> Array<Character> {
let textFieldText: NSString = (text ?? "") as NSString
let textAfterUpdate = textFieldText.replacingCharacters(in: range, with: replacement)
var filtered = Array(textAfterUpdate.filter(validInput.contains))
if filtered.count >= dobPlaceholderText.count {
filtered = Array(filtered[0..<dobPlaceholderText.count])
}
return filtered
}
private func applyPattern(_ filtered: Array<Character>, pattern: Array<Character>) -> String {
var result = pattern
var iter = filtered.makeIterator()
for i in 0..<pattern.count {
if delims.contains(pattern[i]) {
result[i] = pattern[i]
} else if let ch = iter.next() {
result[i] = ch
} else {
result[i] = pattern[i]
}
}
return String(result)
}
private func newCursorPosition(range: NSRange, replacement: String, newText: String) -> Int {
var newPos = 0
if replacement.isEmpty {
newPos = range.location
}
else {
newPos = min(range.location + range.length + 1, dobPlaceholderText.count)
if newPos < dobPlaceholderText.count && delims.contains(Array(newText)[newPos]) {
newPos += 1
}
}
return newPos
}
Demo
Im currently trying to set a max length on a UITextField which works fine as per the UITextField delegate method
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text ?? ""
guard let stringRange = Range(range, in: currentText) else { return false }
let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
return updatedText.count <= 9
}
Tthe issue im having is when using autocomplete with some text already input.
For example, the UITextField is set to have the content type of postal code. Everything works fine if i autocomplete a postcode LS27 8LN or similar from an empty UITextField
An example problem is if i have postcode LS already in the UITextField and i autocomplete LS27 9AL the updatedText in the example code is LSLS27 9AL which goes over my max length. The range also has location and length of 0
One thing i've noticed is if i remove the delegate method all together, it seems iOS replaces the current text within the UITextField anyway.
Try this extension of UITextField
import UIKit
private var __maxLengths = [UITextField: Int]()
extension UITextField{
private struct Constants {
static let defaultMaxLength: Int = 150
}
#IBInspectable var maxLength: Int {
get {
guard let len = __maxLengths[self] else {
return Constants.defaultMaxLength
}
return len
}
set {
__maxLengths[self] = newValue
self.addTarget(self, action: #selector(fix), for: .editingChanged)
}
}
#objc func fix(textField: UITextField) {
let text = textField.text
textField.text = text?.safelyLimitedTo(length: maxLength)
}
func safelyLimitedTo(text: String, length n: Int) -> String {
guard n >= 0, text.count > n else {
return text
}
return String( Array(text).prefix(upTo: n) )
}
}
Them set the maxLenght you need, like textField.maxLength = 20
Newbie in Swift, please help me out, when I type . or after .0 or .00 it should reflect with 0.00, but this is not visible to myTxtField.text.
Following is my code -
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// return NO to not change text
//Decimal
switch string {
case "0","1","2","3","4","5","6","7","8","9",".":
currentString += string
print(currentString)
formatCurrency(currentString)
default:
if string.characters.count == 0 && currentString.characters.count != 0 {
currentString = String(currentString.characters.dropLast())
formatCurrency(currentString)
}
}
return false
}
func formatCurrency(string: String) {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
formatter.maximumFractionDigits = 2
formatter.roundingMode = .RoundDown
formatter.locale = NSLocale(localeIdentifier: "en_US")
let numberFromField = (NSString(string: currentString).doubleValue)
let tAmount:String!
if string == "" {
myTxtField.text = ""
tAmount = "$" + formatter.stringFromNumber(numberFromField)!
}else {
myTxtField.text = "$" + formatter.stringFromNumber(numberFromField)!
tAmount = "$" + formatter.stringFromNumber(numberFromField)!
}
print("value=\(HManager.convertCurrencyToDouble(tAmount))")
}
The following is an example of using shouldChangeCharactersIn delegate method and editingDidEnd to achieve your goal.
The shouldChangeCharactersIn to limit the text field to accepting only number and decimal point ..
The editingDidEnd is to format the text field with 2 decimal digits.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let allowedCharacters = CharacterSet.decimalDigits
if string.rangeOfCharacter(from: allowedCharacters.inverted) != nil {
// If the added character is not a number
if string == "." {
// If the added character is the decimal point
let countDots = textField.text!.components(separatedBy: ".").count - 1
if countDots == 0 {
return true
}
}
return false
}
return true
}
#IBAction func editingDidEnd(_ sender: UITextField) {
// Format the input with 2 decimal digits.
if let input = sender.text, let inputDouble = Double(input) {
sender.text = String(format: "%.2f", inputDouble)
} else {
sender.text = "0.00"
}
}
}
UPDATE
In order to add a currency symbol ($) and remove the fractional part if needed, you need to have a function to check whether the input is double or Int.
#IBAction func editingDidEnd(_ sender: UITextField) {
// Format the input with 2 decimal digits.
if let input = sender.text, let number = Double(input) {
if canConvertToInt(number) {
sender.text = "$\(Int(number))"
} else {
sender.text = "$\(number)"
}
} else {
sender.text = "$0"
}
}
// Check if the number is not decimal
func canConvertToInt(_ double: Double) -> Bool {
return rint(double) == double
}
This code works perfectly, and I can't key in anything other than integers, even when I try to paste it in.
I'd like to add one more refinement, which is to limit the length of the input. Here's my code:
func initializeTextFields()
{
APTeams.delegate = self
APTeams.keyboardType = UIKeyboardType.NumberPad
APRounds.delegate = self
APRounds.keyboardType = UIKeyboardType.NumberPad
APBreakers.delegate = self
APBreakers.keyboardType = UIKeyboardType.NumberPad
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Find out what the text field will be after adding the current edit
let text = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
if text == "" {
return true
}
if let _ = Int(text) {
return true
}
else {
return false
}
}
What do I have to add to it to achieve this? The maximum input length for all the TextFields should be <= 4.
BTW, all code is in Swift 2. From problems I faced when trying to implement answers to questions I've asked before, I gather that some of the methods are different.
count(textField.text) is deprecated in SWIFT 2.0
public func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if let textField = textField as? UITextField {
if (range.length + range.location > textField.text!.characters.count) {
return false;
}
let newLength = textField.text!.characters.count + string.characters.count - range.length;
switch(textField.tag) { //In case you want to handle multiple textfields
case Constants.TAG1:
return newLength <= 20;
case Constants.TAG2:
return newLength <= 30;
default:
return newLength <= 15;
}
}
return true;
}
Write the condition in textfield delegate method as:-
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
if (count(textField.text) > 4 && range.length == 0)
{
return false // return NO to not change text
}
else
{
}
write all your code part in else part.
The delegate methods or an NSFormatter such as NSNumberFormatter.
The formatter is the most appropriate generally as it also provides localization support.
I know its bit too late but still I want share it too, I found a way which is much easier to set a limit character for an textfield in swift development.
Here is the code:-
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(self, action: #selector(limitLength), for: .editingChanged)
}
}
#objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text, prospectiveText.count > maxLength else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
#if swift(>=4.0)
text = String(prospectiveText[..<maxCharIndex])
#else
text = prospectiveText.substring(to: maxCharIndex)
#endif
selectedTextRange = selection
}
}
and just set the limit through the panel.
Image:
Just try this to limit the length of TF
Editing changed Action Outlet of TF
#IBAction func otpTF2EditingChnaged(_ sender: UITextField) {
if (sender.text?.count == 1) {
otpTF3.becomeFirstResponder()
}
checkMaxLength(textField: sender , maxLength: 1)
}
Function That will limit the length
private func checkMaxLength(textField: UITextField!, maxLength: Int) {
if (textField.text!.count > maxLength) {
textField.deleteBackward()
}
}
I have searched all around for this but still cannot seem to get it to work. I have a simple iOS program with a UITextField that converts Farenheit to Celsius and Kelvin. I am trying to limit only 1 decimal entry in this UITextField. For example 98.9 is ok but 98..9 is not.
This is the function in which I want to limit only 1 decimal. What I would like is if 1 decimal is entered then it will not allow another 1 to be entered but numbers can still be entered after the first decimal of course.
func farenheitButton(sender: AnyObject)
{
var fVal = self.farenheitTextLabel.text
var cVal = self.celsiusTextLabel.text
var kVal = self.kelvinTextLabel.text
if let fVal = self.farenheitTextLabel.text?.toDouble()
{
var fValx = self.farenheitTextLabel.text
var fDoubleValue : Double = NSString (string: fValx).doubleValue
var fToC = (fDoubleValue - 32) * (5/9) //convert Farenheit to Celsius formula
var fToK = ((fDoubleValue - 32) / (1.8000)) + 273.15 //convert Farenheit to Kelvin formula
//create string from value with a format
var afToC : Double = fToC
var bfToC : String = String(format:"%.4f",afToC)
var afToK : Double = fToK
var bfToK : String = String(format:"%.4f",afToK)
celsiusTextLabel.text = bfToC
kelvinTextLabel.text = bfToK
}
}
You can set the UITextField.delegate = self and use the textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) method to do what you want.
The following code will not allow you to enter more than 1 decimal point in the textfield.
func textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) -> Bool
{
let countdots = textField.text.componentsSeparatedByString(".").count - 1
if countdots > 0 && string == "."
{
return false
}
return true
}
You should add UITextFieldDelegate to class ViewController:
class ViewController: UIViewController, UITextFieldDelegate {
...
}
then
If name of your #IBOutlet is textField, (it could looks like:
#IBOutlet weak var textField: UITextField!), then add
override func viewDidLoad() {
super.viewDidLoad()
self.textField.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
after that you could use:
func textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) -> Bool
{
let countdots = textField.text.componentsSeparatedByString(".").count - 1
if countdots > 0 || textField.text == "."
{
return false
}
return true
}
You can use this subclass of UITextField. It uses the same principle as the answers above, but with a little bit clearer code.
class WMNumberTextField: UITextField, UITextFieldDelegate {
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = text else { return false }
let dots = text.characters.filter { $0 == "." }
return dots.count == 0 || string != "."
}
}
Swift 3
func textField(_ textField: UITextField,shouldChangeCharactersIn range: NSRange,replacementString string: String) -> Bool
{
let countdots = (textField.text?.components(separatedBy: (".")).count)! - 1
if (countdots > 0 && string == "."){
return false
}
return true
}
I used below code to limit 1 decimal entry in swift 3.0.
let inverseSet = CharacterSet(charactersIn:".0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
if (textField.text?.contains("."))!, string.contains(".") {
return false
}
return string == filtered
Swift 4: This works !
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let counter = textField.text?.components(separatedBy: ".") else { return false }
if (counter.count - 1 > 0 && string == ".") { return false }
return true
}
For swift 5 to restrict text field to enter only one (.) decimal.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == "." {
let countdots = textField.text!.components(separatedBy: ".").count - 1
if countdots == 0 {
return true
} else {
if countdots > 0 && string == "." {
return false
} else {
return true
}
}
}
return true
}