Issue #844

Use custom Binding and validate the text. In case of zero, return empty string to let TextField display placeholder

var textField: some View {
    let text = Binding<String>(
        get: {
            state.amount > 0 ? "$\(state.amount)" : ""
        },
        set: { text in
            let text = text.replacingOccurrences(of: "$", with: "")
            state.amount = Int(text) ?? 0
        }
    )

    return TextField("$0", text: text)
        .keyboardType(.numberPad)
        .font(.system(size: 50, weight: .medium, design: .monospaced))
        .foregroundColor(Asset.textPrimary.color)
        .multilineTextAlignment(.center)
        .accentColor(Asset.primary.color)
}

We can also use NumberFormatter for more customizable control

private extension NumberFormatter {
    static let currency: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.maximumFractionDigits = 2
        formatter.minimumFractionDigits = 0
        return formatter
    }()
}

For decimal pad, and to avoid overflow, we can have some checks

let text = Binding<String>(
    get: {
        if state.payment.amount > 0 {
            let number = NSNumber(value: state.payment.amount)
            let string = NumberFormatter.currency.string(from: number) ?? ""
            return "$\(string)"
        } else {
            return ""
        }
    },
    set: { text in
        let text = text.replacingOccurrences(of: "$", with: "")

        if text.hasSuffix(".") {
            return
        }

        let amount = Amount(text) ?? 0

        // Avoid TextField to overgrow outside frame
        if amount < 10_000 {
            state.payment.amount = amount
        } else {
            state.payment.amount = state.payment.amount
        }
    }
)