iosswiftnumberformatter

Number formatter not allowing decimals to show


Here is the obligatory "I'm new to programming" but, I've searched all available answers and have concluded that my issue may be more logic related than code, but I could be wrong about that too. I'm building a calculator app and everything is working except the numberFormatter (to show comma separators) in the display. Whenever I try to format the number in the display, I can't get the display to show the decimal and the commas.

If I start with a decimal .1234 , I get 0.1234 and if I type 12345 I get 12,345 but if i type 12345.678, I get 12,345. I'm losing the decimals. I've tested it and my function to remove extraneous "." doesn't seem to be the issue. And If I run the string extension numberFormatter outside of the label formatting controls it seems to work, but I need to guard against multiple decimals and extraneous "0"s.

I'm showing the code to the IBAction covering the buttons showing up on the display label, display.text which is the issue. All calculations after this are working fine, with the replacingOccurrences(of: ",", with: "") to create a clean string to convert to Double and calculate.

I'm using a sting extension to do the formatting. I've been working on and off on this for weeks. Any ideas? Do I have to refactor how I enter text into the label.text?

here is the code to add text to the UILabel display.

@IBAction func btnTouchDigit(_ sender: UIButton) {

        let digit = sender.currentTitle!

        if isUserTyping {
            var formattedNumber = ""

            print( "is user typting + String\(isUserTyping)")

            // make sure we aren't adding a second period

            var textCurrentlyInDisplay = display.text
            textCurrentlyInDisplay = textCurrentlyInDisplay?.replacingOccurrences(of: ",", with: "")

            if digit == "." && ((textCurrentlyInDisplay?.range(of: ".")) != nil) {
                return
            }
            else {
                formattedNumber = (textCurrentlyInDisplay! + digit)
                print("formattedNumber = \(formattedNumber.twoFractionDigits)")
                display.text = formattedNumber.twoFractionDigits

                // put code here to format label.text to show thousand seperators
                print("textCurrentlyInDisplay end = \(textCurrentlyInDisplay!)")     
            }
        }

          // make sure we aren't entering a bunch of zero's        
        else { print("else + \(isUserTyping)")

            display.text = digit
            if digit == "0" {return}
            else if digit == "." {display.text = "0."}
            // display.text = (digit == "." ? "0" : "") + digit
            isUserTyping = true
        }

    }

Here is my extension to handle the string conversion for the numberFormatter.

extension String {
    var twoFractionDigits: String {
        let styler = NumberFormatter()
        styler.minimumFractionDigits = 0
       styler.maximumFractionDigits = 16
       styler.numberStyle = .decimal
        let converter = NumberFormatter()
        converter.decimalSeparator = "."
        if let result = converter.number(from: self) {
            return styler.string(from: result)!
        }
        return ""

    }

Solution

  • I found a hack to work around my problem. It's not pretty, but it works. I was able to get the numberFormatter to update and show the digits after the decimal but that led to a new issue. If you typed 12345.00 you would get 12,344 and not see the trailing 0's until you pressed another number. ex 12345.1 -> 12,345.1, 12345.001 -> 12,345.001, but 12345.00 -> 12,345.

    I needed it to work dynamically so the user knows how many zeros are being entered. My hack was to split the final amount into an two arrays. One pre-formatted and one post-formatted. Then join the two together for the final display number.

    I'd still love to find a more efficient solution, if anyone has any ideas.

    Here is the updated code.

    @IBAction func btnTouchDigit(_ sender: UIButton) {
        let digit = sender.currentTitle!
        
        if isUserTyping {
            preFormattedNumber = (display.text?.replacingOccurrences(of: ",", with: ""))!
            
            // set the limit for number of digits user can enter at once
            if display.text!.count >= 16 {
                return
            }  
            else {
                // make sure we aren't adding a second period
                if digit == "." && ((preFormattedNumber.range(of: ".")) != nil) {
                    
                    print("extra decimal pressed")
                    return
                }
                else {
                    preFormattedNumber = (preFormattedNumber + digit)
                    print("preFormattedNumber before Formatting = \(preFormattedNumber)")
                }
                
                // put code here to format label.text to show thousand seperators
                if ((preFormattedNumber.range(of: ".")) != nil){
                    print("just checked for .")
                    let numPreFormat = preFormattedNumber
                    let numAfterFormat = preFormattedNumber.twoFractionDigits
                    let numArray = numPreFormat.components(separatedBy: ".")
                    let numArrayFormatted = numAfterFormat.components(separatedBy: ".")
                    let afterDecimal = numArray.last
                    let beforeDecimal = numArrayFormatted.first
                    let finalNumberToDisplay = beforeDecimal! + "." + afterDecimal!
                    print("numArray = \(numArray)")
                    print("final number to display = \(finalNumberToDisplay)")
                    print("numArray = \(numArray)")
                    display.text = finalNumberToDisplay
                    runningNumber = display.text!
                }
                else {
                    display.text = preFormattedNumber.twoFractionDigits
                    runningNumber = display.text!
                }
            }
        }    
        // make sure we aren't entering a bunch of zero's
        else { print("else + \(isUserTyping)")
            preFormattedNumber = digit
            display.text = preFormattedNumber
            runningNumber = display.text!
            if digit == "0" {return}
            else if digit == "." { preFormattedNumber = "0.";
            }
            
            display.text = preFormattedNumber
            runningNumber = display.text!
            isUserTyping = true
        }
    }