Skip to content

Instantly share code, notes, and snippets.

@damodarnamala
Last active March 24, 2022 14:47
Show Gist options
  • Save damodarnamala/97b25d78ffbce090d4458c506fa7d32d to your computer and use it in GitHub Desktop.
Save damodarnamala/97b25d78ffbce090d4458c506fa7d32d to your computer and use it in GitHub Desktop.
SwiftUI Custom textfield for placeholder and manager currency
//
// ContentView.swift
// Amount
//
// Created by Damodar Namala on 25/02/22.
//
import SwiftUI
import Combine
struct ContentView: View {
@State private var price: Double?
@State var error: AmountError = .none
@State var amountError: AmountError? = .invalidAmount
@State var currency: Currencies = .DOP
@State var shouldEnabled: Bool = false
var body: some View {
ZStack {
VStack {
AmountField(amount: $price,
currency: $currency,
error: $amountError)
AmountField(amount: $price,
currency: $currency,
error: $amountError,
shouldDisable: .constant(true))
Button("Print") {
UIApplication.shared.endEditing()
if let price = price {
print(price)
}
}
Button("DOP 500") {
currency = .DOP
UIApplication.shared.endEditing()
price = 50066
}
Button("USD 400") {
currency = .USD
UIApplication.shared.endEditing()
price = 400663
}
}
}
.onTapGesture {
UIApplication.shared.endEditing()
}
}
}
struct AmountField: View {
@Binding var amount: Double?
@Binding var currency: Currencies
@Binding var error: AmountError?
@Binding var shouldDisable: Bool
init(amount: Binding<Double?>,
currency: Binding<Currencies>,
error: Binding<AmountError?>,
shouldDisable: Binding<Bool> = .constant(false)) {
self._amount = amount
self._currency = currency
self._error = error
self._shouldDisable = shouldDisable
}
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text("Amount")
.font(.headline)
.frame( maxWidth: .infinity, alignment: .leading)
HStack {
DecimalTextField(currency: $currency,
value: $amount,
formatter: formatter(value: "\(amount ?? 0.00)"))
{ isEditing in
self.error = AmountError.none
}
.frame(height: 54, alignment: .center)
.cornerRadius(8)
}
Divider()
if let error = error, let desc = error.errorDescriptiom {
if error != .none && error.errorDescriptiom != nil {
Text(desc)
.foregroundColor(.red)
.font(.footnote)
}
}
}
.cardStyle()
.disabled(shouldDisable)
}
}
typealias AmountBlock = ((Double) -> Void)
enum AmountError {
case insufficientBalance
case invalidAmount
case none
var errorDescriptiom: String? {
switch self {
case .insufficientBalance:
return "insufficientBalance"
case .invalidAmount:
return "Amount must be > 0"
case .none:
return nil
}
}
}
struct DecimalTextField: UIViewRepresentable {
@Binding var currency: Currencies
@Binding var value: Double?
var isEditing: BoolBock?
private let currencyLabel = UILabel()
private var formatter: NumberFormatter
init(currency: Binding<Currencies>,
value: Binding<Double?>,
formatter: NumberFormatter,
isEditing: BoolBock?) {
self._value = value
self.formatter = formatter
self.isEditing = isEditing
self._currency = currency
}
func makeUIView(context: Context) -> TextFieldPadding {
let textfield = TextFieldPadding()
textfield.delegate = context.coordinator
textfield.textAlignment = .left
textfield.keyboardType = .decimalPad
textfield.setContentHuggingPriority(.defaultHigh, for: .vertical)
textfield.setContentHuggingPriority(.defaultLow, for: .horizontal)
textfield.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textfield
}
func updateUIView(_ uiView: TextFieldPadding, context: Context) {
currencyLabel.text = currency.currencyString
uiView.leftViewMode = .always
uiView.leftView = currencyLabel
uiView.placeholder = currency.placeholder
if let val = value {
uiView.text = self.formatter.string(for: val)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self, isEditing: isEditing)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: DecimalTextField
var isEditing: BoolBock?
init(_ textField: DecimalTextField, isEditing: BoolBock?) {
self.parent = textField
self.isEditing = isEditing
}
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
self.isEditing?(true)
return true
}
func textFieldDidEndEditing(_ textField: UITextField,
reason: UITextField.DidEndEditingReason) {
textField.text = textField.text?.formatted()
self.parent.value = textField.text?.toDouble
}
}
}
extension String {
private var remveStrings: String {
return self.replacingOccurrences(of: ",", with: "", options: .numeric)
}
var toDouble: Double {
return Double(self.remveStrings) ?? 0.00
}
func formatted(value: String = "") -> String {
let number = NSNumber(value: self.toDouble)
let formatter = NumberFormatter()
formatter.locale = Locale.usd
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = (value.contains(".00")) ? 0 : 2
guard let res = formatter.string(from: number) else {return self}
return "\(res)"
}
}
func formatter(value: String) -> NumberFormatter {
let formatter = NumberFormatter()
formatter.minimumFractionDigits = (value.contains(".00")) ? 0 : 2
formatter.maximumFractionDigits = 2
formatter.groupingSize = 3
formatter.secondaryGroupingSize = 2
formatter.numberStyle = .decimal
return formatter
}
typealias BoolBock = ((Bool) -> Void)
class TextFieldPadding: UITextField {
let padding = UIEdgeInsets(top: 0, left: 54, bottom: 0, right: 8)
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
}
extension Locale {
static let usd = Locale(identifier: "en_US")
}
/// No need this code
enum Currencies: String, CaseIterable, Identifiable {
var id: UUID { return UUID() }
case USD
case DOP
case RD
var code: String {
switch self {
case .DOP:
return "DOP"
case .RD:
return "DOP"
case .USD:
return "USD"
}
}
var placeholder: String {
return "0.00"
}
var currencyString: String {
switch self {
case .USD:
return "USD$"
case .DOP:
return "DOP$"
case .RD:
return "DOP$"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment