Created
November 1, 2025 22:34
-
-
Save pitt500/8d7ec173418fca024dbbff132a0d8c5b to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| // | |
| // ContentView.swift | |
| // CommaVsPeriodDemo | |
| // | |
| // Created by Pedro Rojas on 18/09/25. | |
| // | |
| import SwiftUI | |
| struct DecimalTextField: View { | |
| let title: String | |
| @Binding var text: String | |
| let locale = Locale.current | |
| var body: some View { | |
| TextField(title, text: $text) | |
| .keyboardType(.decimalPad) | |
| .textInputAutocapitalization(.never) | |
| .disableAutocorrection(true) | |
| .padding(10) | |
| .background(Color(.secondarySystemBackground)) | |
| .clipShape(RoundedRectangle(cornerRadius: 10)) | |
| .onChange(of: text) { _, newValue in | |
| let sanitizedDigits = sanitizeDigits(newValue, locale: locale) | |
| if sanitizedDigits != newValue { | |
| text = sanitizedDigits | |
| } | |
| } | |
| } | |
| private func sanitizeDigits(_ s: String, locale: Locale) -> String { | |
| var result = "" | |
| var seenDecimalSeparator = false | |
| let decimalSeparator = Character(locale.decimalSeparator ?? ".") | |
| // Keep Digits and just one ".". It will discard everything else. | |
| for ch in s { | |
| if ch.isNumber { | |
| result.append(ch) | |
| } else if ch == decimalSeparator && !seenDecimalSeparator { | |
| result.append(decimalSeparator) | |
| seenDecimalSeparator = true | |
| } | |
| } | |
| // Split Integers and fractional parts from "." | |
| let parts = result.split( | |
| separator: decimalSeparator, | |
| maxSplits: 1, | |
| omittingEmptySubsequences: false | |
| ) | |
| let intPart = parts.first.map(String.init) ?? "" | |
| let fractionalPart = parts.count == 2 ? String(decimalSeparator) + parts[1] : "" | |
| // Formatters: one for grouped int display, one for parsing ungrouped ints | |
| return groupedNumber( | |
| intPart: intPart, | |
| fractionalPart: fractionalPart, | |
| locale: locale | |
| ) | |
| } | |
| private func groupedNumber( | |
| intPart: String, | |
| fractionalPart frac: String, | |
| locale: Locale | |
| ) -> String { | |
| let group = NumberFormatter() | |
| group.locale = locale | |
| group.numberStyle = .decimal | |
| group.usesGroupingSeparator = true | |
| let parseInt = NumberFormatter() | |
| parseInt.locale = locale | |
| parseInt.numberStyle = .decimal | |
| parseInt.usesGroupingSeparator = false | |
| // 3) Group integer part using locale rules | |
| let groupedInt = parseInt.number(from: intPart).flatMap { | |
| group.string(from: $0) | |
| } ?? intPart | |
| return groupedInt + frac | |
| } | |
| } | |
| struct ContentView: View { | |
| @State private var text = "" | |
| @State private var amount = "" | |
| var body: some View { | |
| VStack(alignment: .leading) { | |
| Text("Amount you want to transfer:") | |
| DecimalTextField(title: "Enter amount", text: $text) | |
| Button { | |
| amount = calculateAmount() | |
| } label: { | |
| Text("Send") | |
| } | |
| .frame(maxWidth: .infinity, alignment: .center) | |
| .padding() | |
| Text("Region: \(region)") | |
| Text(amount.isEmpty ? "" : "Total: \(amount)") | |
| } | |
| .padding(20) | |
| } | |
| private func calculateAmount() -> String { | |
| let formatter = NumberFormatter() | |
| formatter.numberStyle = .decimal | |
| formatter.maximumFractionDigits = 2 | |
| guard let nsNumber = formatter.number(from: text) else { | |
| return "Error" | |
| } | |
| return "$\(formatter.string(from: nsNumber) ?? "Error")" | |
| } | |
| private var region: String { | |
| Locale.current.localizedString(forRegionCode: Locale.current.region?.identifier ?? "Unknown") ?? "Unknown" | |
| } | |
| } | |
| #Preview { | |
| ContentView() | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment