Last active
October 21, 2023 08:43
-
-
Save haojianzong/8b8b2554d9f84adbadae9e19d7e81e03 to your computer and use it in GitHub Desktop.
UITextField with groupingSeparator when inputting number, tested with localization support.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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