Skip to content

Instantly share code, notes, and snippets.

@haojianzong
Last active October 21, 2023 08:43
Show Gist options
  • Save haojianzong/8b8b2554d9f84adbadae9e19d7e81e03 to your computer and use it in GitHub Desktop.
Save haojianzong/8b8b2554d9f84adbadae9e19d7e81e03 to your computer and use it in GitHub Desktop.
UITextField with groupingSeparator when inputting number, tested with localization support.
import Foundation
import UIKit
// Implements UITextFieldDelegate to support input features like showing thousand separator
// Useful for financial App currency number input, tested for multiple localizations, copy and paste numbers into the field.
// Usage:
//
// 1. Setup
// let textField = UITextField()
// let coordinator = DecimalTextFieldCoordinator(balanceTextField)
// textField.delegate = coordinator
//
// 2. Get the value from the text field:
// balanceTextFieldCoordinator.value
class DecimalTextFieldCoordinator: NSObject, UITextFieldDelegate {
static func formatter(digits: Int) -> NumberFormatter {
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .decimal
formatter.usesSignificantDigits = false
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = digits
return formatter
}
static func allowSaveStoreOnTyping(currentText: String?) -> Bool {
if currentText?.contains(NumberFormatter().decimalSeparator) == true,
let last = currentText?.last,
NSMutableCharacterSet(charactersIn: NumberFormatter().decimalSeparator + "0").isSuperset(of: CharacterSet(charactersIn: String(last))) {
// Don't change stored value when user is typing decimal value
return false
} else {
return true
}
}
static func allowInput(currentText: String?, string: String) -> Bool {
let mutableSet = NSMutableCharacterSet(charactersIn: NumberFormatter().decimalSeparator)
mutableSet.formUnion(with: NSCharacterSet.decimalDigits)
mutableSet.addCharacters(in: NumberFormatter().groupingSeparator)
let isNumberSet = mutableSet.isSuperset(of: CharacterSet(charactersIn: string))
return isNumberSet
}
private let formatter: NumberFormatter
let textField: UITextField
init(_ textField: UITextField) {
self.textField = textField
self.formatter = Self.formatter(digits: .percentoMaxDigits)
super.init()
self.textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
@objc func textFieldDidChange(_ textField: UITextField) {
guard DecimalTextFieldCoordinator.allowSaveStoreOnTyping(currentText: textField.text) else {
return
}
textField.text = formatter.string(for: value)
}
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
return DecimalTextFieldCoordinator.allowInput(currentText: textField.text, string: string)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
// clear the text if 0
if value == 0 {
textField.text = ""
}
}
private func valueFor(text: String?) -> Decimal {
let originalText = text?.replacingOccurrences(of: NumberFormatter().groupingSeparator, with: "")
if let currentValue = originalText {
let number = formatter.number(from: currentValue) ?? 0.0
let result = number.decimalValue
return result
} else {
return 0
}
}
var value: Decimal {
valueFor(text: textField.text)
}
func textFieldDidEndEditing(_ textField: UITextField,
reason: UITextField.DidEndEditingReason) {
textField.text = formatter.string(for: value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment