Home > Software engineering >  Default Value for UITextField in SwiftUI
Default Value for UITextField in SwiftUI

Time:04-25

I've tried to implement Currency TextField to my project, which I found on this website - https://benoitpasquier.com/currency-textfield-in-swiftui.

It almost does work as expected, but with one major issue: passed value is not showing up in TextField. (eg. Binding = 1000 is still presented as $0.00 in TextField)

Code below:

import Foundation
import SwiftUI
import UIKit

class CurrencyUITextField: UITextField {
    @Binding private var value: Int
    private let formatter: NumberFormatterProtocol

    init(formatter: NumberFormatterProtocol, value: Binding<Int>) {
        self.formatter = formatter
        self._value = value
        super.init(frame: .zero)
        setupViews()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func willMove(toSuperview newSuperview: UIView?) {
        super.willMove(toSuperview: superview)
        addTarget(self, action: #selector(editingChanged), for: .editingChanged)
        addTarget(self, action: #selector(resetSelection), for: .allTouchEvents)
        keyboardType = .numberPad
        textAlignment = .right
        sendActions(for: .editingChanged)
    }

    override func removeFromSuperview() {
        Log.info("Removed CurrencyTextField from View")
    }

    override func deleteBackward() {
        text = textValue.digits.dropLast().string
        sendActions(for: .editingChanged)
    }

    private func setupViews() {
        tintColor = .clear
        font = .systemFont(ofSize: 40, weight: .regular)
    }

    @objc private func editingChanged() {
        text = currency(from: decimal)
        resetSelection()
        updateValue()
    }

    @objc private func resetSelection() {
        selectedTextRange = textRange(from: endOfDocument, to: endOfDocument)
    }

    private func updateValue() {
        DispatchQueue.main.async { [weak self] in
            self?.value = self?.intValue ?? 0
        }
    }

    private var textValue: String {
        return text ?? ""
    }

    private var decimal: Decimal {
        return textValue.decimal / pow(10, formatter.maximumFractionDigits)
    }

    private var intValue: Int {
        return NSDecimalNumber(decimal: decimal * 100).intValue
    }

    private func currency(from decimal: Decimal) -> String {
        return formatter.string(for: decimal) ?? ""
    }
}

extension StringProtocol where Self: RangeReplaceableCollection {
    var digits: Self { filter(\.isWholeNumber) }
}

extension String {
    var decimal: Decimal { Decimal(string: digits) ?? 0 }
}

extension LosslessStringConvertible {
    var string: String { .init(self) }
}
import SwiftUI
import UIKit

struct CurrencyTextField: View {
    @Binding var value: Int
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 15)
                .foregroundColor(.quaternary)
                .frame(maxHeight: 80)
            CurrencyTextField_(numberFormatter: NumberFormatter.currencyFormatter, value: $value)
                .padding(.horizontal)
        }
        .frame(maxHeight: 80)
    }
}

struct CurrencyTextField_: UIViewRepresentable {
    typealias UIViewType = CurrencyUITextField

    let numberFormatter: NumberFormatterProtocol
    let currencyField: CurrencyUITextField

    init(numberFormatter: NumberFormatterProtocol, value: Binding<Int>) {
        self.numberFormatter = numberFormatter
        currencyField = CurrencyUITextField(formatter: numberFormatter, value: value)
    }

    func makeUIView(context: Context) -> CurrencyUITextField {
        return currencyField
    }

    func updateUIView(_ uiView: CurrencyUITextField, context: Context) {}
}

struct CurrencyTextField_Previews: PreviewProvider {
    static var previews: some View {
        CurrencyTextField(value: .constant(1025))
    }
}

This view can be initialised with amount = 0, or as an 'Edit' view, with injected amount value (eg. 1000, previously created by the same CurrencyTextField)

struct CreateTransactionView: View {
    @State var amount = 0

    var body: some View {
            VStack(alignment: .leading, spacing: 15) {
                CurrencyTextField(value: $amount)
            }
    }
}

What could be possibly causing this issue? PS. I have little to no experience with UIKit.

CodePudding user response:

updateUIView hasn't been implemented. And makeUIView isn't done correctly.

CodePudding user response:

You can use currency in TextField by changing the format which is only available for iOS 15 .

@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
extension FormatStyle {
    public static func currency<Value>(code: String) -> Self where Self == FloatingPointFormatStyle<Value>.Currency, Value : BinaryFloatingPoint
}
struct ContentView: View {
    @State private var myMoney: Double? = 300.0
    var body: some View {
        TextField(
            "Currency (USD)",
            value: $myMoney,
            format: .currency(code: "USD")
        )
    }
}

or using Locale

struct ContentView: View {
    private let locale = Locale.current
    @State private var myMoney: Double? = 1000.0
    var body: some View {
        TextField(
            "Currency (\(locale.currencyCode ?? "USD")",
            value: $myMoney,
            format: .currency(code: locale.currencyCode ?? "USD")
        )
    }
}
  • Related