2

I want to give my textfield a Currency format so when I type it looks like:

$0.00
$0.09
$0.98
$9.87
$98.76

  • Why do you need RxSwift to format text in a text field? There are many existing examples showing how to format a text field as you type, including as currency. – rmaddy Sep 16 '19 at 22:10
  • @rmaddy some of the examples I found use Notifications such as textDidChange/ textShouldChange, I just want to keep my code with just Rx sequences. – Sebastian Guerrero Sep 16 '19 at 22:15
  • Those are not notifications. Those are delegate methods. Big difference. – rmaddy Sep 16 '19 at 22:16
  • Sorry my bad, got confused, so my main goals is to Not use delegates since I'm using Rx, i know they both can live in the same project – Sebastian Guerrero Sep 16 '19 at 22:23

1 Answers1

9

So what I did was:

An extension of ObservableType with 2 functions, one for transform into the desired format and other to read the value:

extension ObservableType where E == String {

    func currencyFormattedToValue() -> Observable<E> {
        return asObservable().flatMap { currencyString -> Observable<E> in
          let formatter = Constants.moneyFormatter
          let value = Double(truncating: formatter.number(from: currencyString) ?? 0.00).rounded(toPlaces: 2)
          return Observable.just("\(value)")
        }
    }

    func valueToCurrencyFormatted() -> Observable<E> {
        return asObservable().flatMap { valueString -> Observable<E> in
          let formatter = Constants.moneyFormatter
          var amountWithPrefix = valueString
          var number: NSNumber!

          let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
          amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, valueString.count), withTemplate: "")

          let double = (amountWithPrefix as NSString).doubleValue
          number = NSNumber(value: (double / 100))

          guard number != 0 as NSNumber else {
            return Observable.just("$0.00")
          }
          return Observable.just(formatter.string(from: number)!)
      }
  }
}

And then I used them like so:

textField.rx.text
    .orEmpty
    .changed
    .valueToCurrencyFormatted()
    .bind(to: self.textField.rx.text)
    .disposed(by: disposeBag)

and to read the value and bind it to a subject:

textField.rx.text
  .orEmpty
  .currencyFormattedToValue()
  .bind(to: viewModel.amountValue)
  .disposed(by: disposeBag)

My Formatter:

class Constants {
  static var moneyFormatter:NumberFormatter {
    let formatter = NumberFormatter()
    formatter.numberStyle = .currencyAccounting
    formatter.currencySymbol = "$"
    formatter.maximumFractionDigits = 2
    formatter.minimumFractionDigits = 2

    return formatter
  }
}

I'm open tu suggestions, thanks!

  • 2
    What you have is good except it will push the cursor to the end of the text field every time the user changes any value. So for example if the user select the digits on the left side of the `.` and types "25", they would expect to see "$25.00" but instead they would get "$2.05" or maybe even "$0.05". To fix that problem, use this: https://gist.github.com/danielt1263/cacfd3f7cd55adfe4cedba990d751d38 – Daniel T. Sep 17 '19 at 10:46