Explorar el Código

fix: prevent wrong decimal separator input in TextFieldWithToolBar

Previously it was possible to enter both decimal separators (comma and point)
regardless of the locale settings. This could lead to parsing errors and
incorrect number formatting.

Changes:
- Only allow decimal separator configured in current locale
- Reject input of wrong decimal separator (e.g. point in comma locale)
- Keep existing validation for multiple decimal separators

fix #274
polscm32 hace 1 año
padre
commit
262f63f2e0
Se han modificado 1 ficheros con 49 adiciones y 34 borrados
  1. 49 34
      Trio/Sources/Views/TextFieldWithToolBar.swift

+ 49 - 34
Trio/Sources/Views/TextFieldWithToolBar.swift

@@ -185,6 +185,16 @@ public struct TextFieldWithToolBar: UIViewRepresentable {
 }
 
 extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate {
+    public func textFieldDidEndEditing(_ textField: UITextField) {
+        if let text = textField.text,
+           let decimal = Decimal(string: text, locale: parent.numberFormatter.locale)
+        {
+            // Format the number properly when editing ends
+            textField.text = parent.numberFormatter.string(from: decimal as NSNumber)
+            parent.text = decimal
+        }
+    }
+
     public func textField(
         _ textField: UITextField,
         shouldChangeCharactersIn range: NSRange,
@@ -192,52 +202,57 @@ extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate {
     ) -> Bool {
         // Check if the input is a number or the decimal separator
         let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string))
-        let isDecimalSeparator = (string == decimalFormatter.decimalSeparator && textField.text?.contains(string) == false)
 
-        // Only proceed if the input is a valid number or decimal separator
-        if isNumber || isDecimalSeparator && parent.allowDecimalSeparator,
+        // Get the current locale's decimal separator
+        let currentDecimalSeparator = parent.numberFormatter.decimalSeparator ?? "."
+
+        // Check if input is a decimal separator (either . or ,)
+        let isInputDecimalSeparator = string == "." || string == ","
+
+        // Only allow the decimal separator configured in the locale
+        if isInputDecimalSeparator {
+            // If it's not the correct decimal separator for this locale, reject it
+            if string != currentDecimalSeparator {
+                return false
+            }
+            // Check if the field already contains a decimal separator
+            if textField.text?.contains(currentDecimalSeparator) == true {
+                return false
+            }
+        }
+
+        // Only proceed if the input is a valid number or the correct decimal separator
+        if isNumber || (string == currentDecimalSeparator && parent.allowDecimalSeparator),
            let currentText = textField.text as NSString?
         {
-            // Get the proposed new text
-            let proposedTextOriginal = currentText.replacingCharacters(in: range, with: string)
+            // Calculate the new text length
+            let newLength = currentText.length + string.count - range.length
 
-            // Remove thousand separator
-            let proposedText = proposedTextOriginal.replacingOccurrences(of: decimalFormatter.groupingSeparator, with: "")
-
-            // Try to convert proposed text to number
-            let number = parent.numberFormatter.number(from: proposedText) ?? decimalFormatter.number(from: proposedText)
+            // Check max length if specified
+            if let maxLength = parent.maxLength, newLength > maxLength {
+                return false
+            }
 
-            let decimalPlacesCurrent = calculateDecimalPlaces(in: currentText as String)
-            let maxDecimalPlaces = parent.numberFormatter.maximumFractionDigits
-            let isCursorAfterDecimal = isCursorAfterDecimal(in: textField, range: range)
+            // Create the new text string
+            let newText = currentText.replacingCharacters(in: range, with: string)
 
-            if decimalPlacesCurrent >= maxDecimalPlaces,
-               range.length == 0,
-               isCursorAfterDecimal
-            {
+            // If text starts with decimal separator, add leading zero
+            if newText.hasPrefix(currentDecimalSeparator) {
+                textField.text = "0" + newText
+                parent.text = Decimal(string: textField.text ?? "0") ?? 0
                 return false
             }
 
-            // Update the binding value if conversion is successful
-            if let number = number {
-                let lastCharIndex = proposedText.index(before: proposedText.endIndex)
-                let hasDecimalSeparator = proposedText.contains(decimalFormatter.decimalSeparator)
-                let hasTrailingZeros = (hasDecimalSeparator && proposedText[lastCharIndex] == "0") || isDecimalSeparator
-                if !hasTrailingZeros
-                {
-                    DispatchQueue.main.async {
-                        self.parent.text = number.decimalValue
-                    }
-                }
-            } else {
-                DispatchQueue.main.async {
-                    self.parent.text = 0
-                }
+            // Update the binding
+            if let decimal = Decimal(string: newText, locale: parent.numberFormatter.locale) {
+                parent.text = decimal
             }
+
+            return true
         }
 
-        // Allow the change if it's a valid number or decimal separator
-        return isNumber || isDecimalSeparator && parent.allowDecimalSeparator
+        // Allow the change if it's a valid number or the correct decimal separator
+        return isNumber || (string == currentDecimalSeparator && parent.allowDecimalSeparator)
     }
 
     public func textFieldDidBeginEditing(_: UITextField) {