From 16be6426b3a44758034b1767171ef1629b2ddeed Mon Sep 17 00:00:00 2001 From: Jose Galindo Date: Sun, 14 Mar 2021 08:50:20 -0600 Subject: [PATCH 1/3] Add option to specify currency symbol. Updated documentation --- README.md | 4 +++ .../SwiftUIKit/views/CurrencyTextField.swift | 29 ++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f182591..4eb559e 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,11 @@ struct ContentView: View { @State private var value = 0.0 var body: some View { + //Minimal configuration CurrencyTextField("Amount", value: self.$value) + + //All configurations + CurrencyTextField("Amount", value: self.$value, alwaysShowFractions: false, numberOfDecimalPlaces: 2, currencySymbol: "US$") .font(.largeTitle) .multilineTextAlignment(TextAlignment.center) } diff --git a/Sources/SwiftUIKit/views/CurrencyTextField.swift b/Sources/SwiftUIKit/views/CurrencyTextField.swift index 75694d2..2cd71f1 100644 --- a/Sources/SwiftUIKit/views/CurrencyTextField.swift +++ b/Sources/SwiftUIKit/views/CurrencyTextField.swift @@ -15,6 +15,7 @@ public struct CurrencyTextField: UIViewRepresentable { private var tag: Int private var alwaysShowFractions: Bool private var numberOfDecimalPlaces: Int + private var currencySymbol: String? private var placeholder: String @@ -46,6 +47,7 @@ public struct CurrencyTextField: UIViewRepresentable { tag: Int = 0, alwaysShowFractions: Bool = false, numberOfDecimalPlaces: Int = 2, + currencySymbol: String? = nil, font: UIFont? = nil, foregroundColor: UIColor? = nil, accentColor: UIColor? = nil, @@ -67,6 +69,7 @@ public struct CurrencyTextField: UIViewRepresentable { self.tag = tag self.alwaysShowFractions = alwaysShowFractions self.numberOfDecimalPlaces = numberOfDecimalPlaces + self.currencySymbol = currencySymbol self.font = font self.foregroundColor = foregroundColor @@ -94,7 +97,7 @@ public struct CurrencyTextField: UIViewRepresentable { // initial value if let v = self.value { - textField.text = v.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions) + textField.text = v.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol) } // tag @@ -178,7 +181,7 @@ public struct CurrencyTextField: UIViewRepresentable { } public func makeCoordinator() -> CurrencyTextField.Coordinator { - Coordinator(value: $value, isResponder: self.isResponder, alwaysShowFractions: self.alwaysShowFractions, numberOfDecimalPlaces: self.numberOfDecimalPlaces, onReturn: self.onReturn){ flag in + Coordinator(value: $value, isResponder: self.isResponder, alwaysShowFractions: self.alwaysShowFractions, numberOfDecimalPlaces: self.numberOfDecimalPlaces, currencySymbol: self.currencySymbol, onReturn: self.onReturn){ flag in self.onEditingChanged(flag) } } @@ -190,7 +193,7 @@ public struct CurrencyTextField: UIViewRepresentable { if self.value == nil { textField.text = nil } else { - textField.text = self.value!.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions) + textField.text = self.value!.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol) } } @@ -216,6 +219,8 @@ public struct CurrencyTextField: UIViewRepresentable { private var onReturn: ()->() private var alwaysShowFractions: Bool private var numberOfDecimalPlaces: Int + private var currencySymbol: String? + var internalValue: Double? var onEditingChanged: (Bool)->() var didBecomeFirstResponder = false @@ -224,6 +229,7 @@ public struct CurrencyTextField: UIViewRepresentable { isResponder: Binding?, alwaysShowFractions: Bool, numberOfDecimalPlaces: Int, + currencySymbol: String?, onReturn: @escaping () -> Void = {}, onEditingChanged: @escaping (Bool) -> Void = { _ in } ) { @@ -233,6 +239,7 @@ public struct CurrencyTextField: UIViewRepresentable { self.isResponder = isResponder self.alwaysShowFractions = alwaysShowFractions self.numberOfDecimalPlaces = numberOfDecimalPlaces + self.currencySymbol = currencySymbol self.onReturn = onReturn self.onEditingChanged = onEditingChanged } @@ -242,7 +249,7 @@ public struct CurrencyTextField: UIViewRepresentable { let originalText = textField.text let text = textField.text as NSString? let newValue = text?.replacingCharacters(in: range, with: string) - let display = newValue?.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces) + let display = newValue?.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, currencySymbol: self.currencySymbol) // validate change if !shouldAllowChange(oldValue: textField.text ?? "", newValue: newValue ?? "") { @@ -323,7 +330,7 @@ public struct CurrencyTextField: UIViewRepresentable { } public func textFieldDidEndEditing(_ textField: UITextField) { - textField.text = self.value?.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions) + textField.text = self.value?.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol) DispatchQueue.main.async { self.isResponder?.wrappedValue = false } @@ -390,7 +397,7 @@ fileprivate extension String { // args: // decimalPlaces - the max number of decimal places - func currencyFormat(decimalPlaces: Int? = nil) -> String? { + func currencyFormat(decimalPlaces: Int? = nil, currencySymbol: String? = nil) -> String? { // uses self.double // logic for varying the number of fraction digits guard let double = double else { @@ -417,6 +424,10 @@ fileprivate extension String { return formatted } + if currencySymbol != nil { + formatter.currencySymbol = currencySymbol + } + formatter.maximumFractionDigits = 0 let formatted = formatter.string(from: NSNumber(value: double)) return formatted @@ -427,7 +438,7 @@ fileprivate extension Double { // args: // decimalPlaces - number of decimal places // forceShowDecimalPlaces - whether to force show fractions - func currencyFormat(decimalPlaces: Int? = nil, forceShowDecimalPlaces: Bool = false) -> String? { + func currencyFormat(decimalPlaces: Int? = nil, forceShowDecimalPlaces: Bool = false, currencySymbol: String? = nil) -> String? { let formatter = Formatter.currency var integer = 0.0 let d = decimalPlaces != nil ? decimalPlaces! : 2 @@ -444,6 +455,10 @@ fileprivate extension Double { formatter.maximumFractionDigits = 0 } } + + if currencySymbol != nil { + formatter.currencySymbol = currencySymbol + } return formatter.string(from: NSNumber(value: self)) } From d01a8640940f49156823f189fee57b7d00611c4b Mon Sep 17 00:00:00 2001 From: Alex Taffe Date: Wed, 11 Aug 2021 09:47:23 -0400 Subject: [PATCH 2/3] Fix dark mode color switching The existing text color implementation did not use the automatic color changing built into UIColor, but rather set the color at runtime. Closes #27 --- Sources/SwiftUIKit/views/CurrencyTextField.swift | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Sources/SwiftUIKit/views/CurrencyTextField.swift b/Sources/SwiftUIKit/views/CurrencyTextField.swift index 2cd71f1..d28165d 100644 --- a/Sources/SwiftUIKit/views/CurrencyTextField.swift +++ b/Sources/SwiftUIKit/views/CurrencyTextField.swift @@ -148,14 +148,7 @@ public struct CurrencyTextField: UIViewRepresentable { } // color - switch context.environment.colorScheme { - case .dark: - textField.textColor = .white - case .light: - textField.textColor = .black - @unknown default: - break - } + textField.textColor = UIColor.label if let fgc = self.foregroundColor { textField.textColor = fgc } From 561dc1863c85030699a37a5725423330af8f27f3 Mon Sep 17 00:00:00 2001 From: veeresh Date: Thu, 7 Apr 2022 12:39:28 +0530 Subject: [PATCH 3/3] Given option in CurrencyTextField have a fixed locale. Increased the character limit from 9 to 20 characters. --- .../SwiftUIKit/views/CurrencyTextField.swift | 97 +++++++++++++++---- 1 file changed, 76 insertions(+), 21 deletions(-) diff --git a/Sources/SwiftUIKit/views/CurrencyTextField.swift b/Sources/SwiftUIKit/views/CurrencyTextField.swift index d28165d..249571f 100644 --- a/Sources/SwiftUIKit/views/CurrencyTextField.swift +++ b/Sources/SwiftUIKit/views/CurrencyTextField.swift @@ -1,6 +1,6 @@ // // File.swift -// +// // // Created by Youjin Phea on 5/05/20. // @@ -36,10 +36,59 @@ public struct CurrencyTextField: UIViewRepresentable { private var onReturn: () -> Void private var onEditingChanged: (Bool) -> Void + private var locale : Locale? = .current @Environment(\.layoutDirection) private var layoutDirection: LayoutDirection @Environment(\.font) private var swiftUIfont: Font? + public init( + _ placeholder: String = "", + value: Binding, + isResponder: Binding? = nil, + tag: Int = 0, + alwaysShowFractions: Bool = false, + numberOfDecimalPlaces: Int = 2, + currencySymbol: String? = nil, + font: UIFont? = nil, + foregroundColor: UIColor? = nil, + accentColor: UIColor? = nil, + textAlignment: NSTextAlignment? = nil, + contentType: UITextContentType? = nil, + autocorrection: UITextAutocorrectionType = .default, + autocapitalization: UITextAutocapitalizationType = .sentences, + keyboardType: UIKeyboardType = .decimalPad, + returnKeyType: UIReturnKeyType = .default, + isSecure: Bool = false, + isUserInteractionEnabled: Bool = true, + clearsOnBeginEditing: Bool = false, + locale : Locale, + onReturn: @escaping () -> Void = {}, + onEditingChanged: @escaping (Bool) -> Void = { _ in } + ) { + self._value = value + self.placeholder = placeholder + self.isResponder = isResponder + self.tag = tag + self.alwaysShowFractions = alwaysShowFractions + self.numberOfDecimalPlaces = numberOfDecimalPlaces + self.currencySymbol = currencySymbol + + self.font = font + self.foregroundColor = foregroundColor + self.accentColor = accentColor + self.textAlignment = textAlignment + self.contentType = contentType + self.autocorrection = autocorrection + self.autocapitalization = autocapitalization + self.keyboardType = keyboardType + self.returnKeyType = returnKeyType + self.isSecure = isSecure + self.isUserInteractionEnabled = isUserInteractionEnabled + self.clearsOnBeginEditing = clearsOnBeginEditing + self.locale = locale + self.onReturn = onReturn + self.onEditingChanged = onEditingChanged + } public init( _ placeholder: String = "", value: Binding, @@ -97,7 +146,7 @@ public struct CurrencyTextField: UIViewRepresentable { // initial value if let v = self.value { - textField.text = v.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol) + textField.text = v.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol,locale : locale ?? .current) } // tag @@ -174,7 +223,7 @@ public struct CurrencyTextField: UIViewRepresentable { } public func makeCoordinator() -> CurrencyTextField.Coordinator { - Coordinator(value: $value, isResponder: self.isResponder, alwaysShowFractions: self.alwaysShowFractions, numberOfDecimalPlaces: self.numberOfDecimalPlaces, currencySymbol: self.currencySymbol, onReturn: self.onReturn){ flag in + Coordinator(value: $value, isResponder: self.isResponder, alwaysShowFractions: self.alwaysShowFractions, numberOfDecimalPlaces: self.numberOfDecimalPlaces, currencySymbol: self.currencySymbol, locale: locale ?? .current, onReturn: self.onReturn){ flag in self.onEditingChanged(flag) } } @@ -186,15 +235,15 @@ public struct CurrencyTextField: UIViewRepresentable { if self.value == nil { textField.text = nil } else { - textField.text = self.value!.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol) + textField.text = self.value!.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol,locale : locale ?? .current) } } // set first responder ONCE // other times, let textfield handle it if self.isResponder?.wrappedValue == true && !textField.isFirstResponder && !context.coordinator.didBecomeFirstResponder { - textField.becomeFirstResponder() - context.coordinator.didBecomeFirstResponder = true + textField.becomeFirstResponder() + context.coordinator.didBecomeFirstResponder = true } // to dismiss, use dismissKeyboard() @@ -213,16 +262,19 @@ public struct CurrencyTextField: UIViewRepresentable { private var alwaysShowFractions: Bool private var numberOfDecimalPlaces: Int private var currencySymbol: String? + private var locale : Locale var internalValue: Double? var onEditingChanged: (Bool)->() var didBecomeFirstResponder = false + init(value: Binding, isResponder: Binding?, alwaysShowFractions: Bool, numberOfDecimalPlaces: Int, currencySymbol: String?, + locale : Locale, onReturn: @escaping () -> Void = {}, onEditingChanged: @escaping (Bool) -> Void = { _ in } ) { @@ -233,6 +285,7 @@ public struct CurrencyTextField: UIViewRepresentable { self.alwaysShowFractions = alwaysShowFractions self.numberOfDecimalPlaces = numberOfDecimalPlaces self.currencySymbol = currencySymbol + self.locale = locale self.onReturn = onReturn self.onEditingChanged = onEditingChanged } @@ -242,7 +295,7 @@ public struct CurrencyTextField: UIViewRepresentable { let originalText = textField.text let text = textField.text as NSString? let newValue = text?.replacingCharacters(in: range, with: string) - let display = newValue?.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, currencySymbol: self.currencySymbol) + let display = newValue?.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, currencySymbol: self.currencySymbol,locale : locale) // validate change if !shouldAllowChange(oldValue: textField.text ?? "", newValue: newValue ?? "") { @@ -280,16 +333,16 @@ public struct CurrencyTextField: UIViewRepresentable { if let cursorLoc = cursorLocation { /** - Shortly after new text is being pasted from the clipboard, UITextField receives a new value for its - `selectedTextRange` property from the system. This new range is not consistent to the formatted text and - calculated caret position most of the time, yet it's being assigned just after setCaretPosition call. - - To insure correct caret position is set, `selectedTextRange` is assigned asynchronously. - (presumably after a vanishingly small delay) - */ + Shortly after new text is being pasted from the clipboard, UITextField receives a new value for its + `selectedTextRange` property from the system. This new range is not consistent to the formatted text and + calculated caret position most of the time, yet it's being assigned just after setCaretPosition call. + + To insure correct caret position is set, `selectedTextRange` is assigned asynchronously. + (presumably after a vanishingly small delay) + */ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { textField.selectedTextRange = textField.textRange(from: cursorLoc, to: cursorLoc) - } + } } // prevent from going to didChange @@ -304,7 +357,7 @@ public struct CurrencyTextField: UIViewRepresentable { } // limits integers length - if newValue.integers.count > 9 { + if newValue.integers.count > 20 { return false } @@ -323,7 +376,7 @@ public struct CurrencyTextField: UIViewRepresentable { } public func textFieldDidEndEditing(_ textField: UITextField) { - textField.text = self.value?.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol) + textField.text = self.value?.currencyFormat(decimalPlaces: self.numberOfDecimalPlaces, forceShowDecimalPlaces: self.alwaysShowFractions, currencySymbol: self.currencySymbol,locale : locale) DispatchQueue.main.async { self.isResponder?.wrappedValue = false } @@ -390,7 +443,7 @@ fileprivate extension String { // args: // decimalPlaces - the max number of decimal places - func currencyFormat(decimalPlaces: Int? = nil, currencySymbol: String? = nil) -> String? { + func currencyFormat(decimalPlaces: Int? = nil, currencySymbol: String? = nil,locale : Locale) -> String? { // uses self.double // logic for varying the number of fraction digits guard let double = double else { @@ -398,6 +451,7 @@ fileprivate extension String { } let formatter = Formatter.currency + formatter.locale = locale // if has fractions, show fractions if fractions != nil { @@ -431,11 +485,12 @@ fileprivate extension Double { // args: // decimalPlaces - number of decimal places // forceShowDecimalPlaces - whether to force show fractions - func currencyFormat(decimalPlaces: Int? = nil, forceShowDecimalPlaces: Bool = false, currencySymbol: String? = nil) -> String? { + func currencyFormat(decimalPlaces: Int? = nil, forceShowDecimalPlaces: Bool = false, currencySymbol: String? = nil,locale : Locale) -> String? { let formatter = Formatter.currency + formatter.locale = locale var integer = 0.0 let d = decimalPlaces != nil ? decimalPlaces! : 2 - + if forceShowDecimalPlaces { formatter.minimumFractionDigits = d formatter.maximumFractionDigits = d @@ -452,7 +507,7 @@ fileprivate extension Double { if currencySymbol != nil { formatter.currencySymbol = currencySymbol } - + return formatter.string(from: NSNumber(value: self)) } }