Swift Calculator - Cannot enter Zero after decimal - swift

I am exploring a Swift Calculator Code and everything works fine except that I am not able to input a Zero after the decimal sign (for ex.: 12,001 or 1,301) and I cannot find the solution. I tried already a few things and unfortunately I cannot find a question solving this issue.
Thanks a lot for your help!
Here are the main parts of the code.
private var total: Double = 0
private var temp: Double = 0
private var operating = false
private var decimal = false
private var operation: OperationType = .none
private let kDecimalSeparator = Locale.current.decimalSeparator!
private let kMaxLength = 11
private let kTotal = "total"
private enum OperationType {
case none, addition, substraction, multiplication, division, percent
}
// Format
private let auxFormatter: NumberFormatter = {
let formatter = NumberFormatter()
let locale = Locale.current
formatter.decimalSeparator = locale.decimalSeparator
formatter.numberStyle = .decimal
formatter.maximumIntegerDigits = 100
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 100
return formatter
}()
// Format result
private let auxTotalFormatter: NumberFormatter = {
let formatter = NumberFormatter()
let locale = Locale.current
formatter.decimalSeparator = locale.decimalSeparator
formatter.numberStyle = .decimal
formatter.maximumIntegerDigits = 100
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 100
return formatter
}()
// Default screen format
private let printFormatter: NumberFormatter = {
let formatter = NumberFormatter()
let locale = Locale.current
formatter.decimalSeparator = locale.decimalSeparator
formatter.numberStyle = .decimal
formatter.maximumIntegerDigits = 9
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 8
return formatter
}()
#IBAction func numberDecimalAction(_ sender: UIButton) {
let currentTemp = auxTotalFormatter.string(from: NSNumber(value: temp))!
if resultLabel.text?.contains(kDecimalSeparator) ?? false || (!operating && currentTemp.count >= kMaxLength) {
return
}
resultLabel.text = resultLabel.text! + kDecimalSeparator
decimal = true
selectVisualOperation()
sender.shine()
}
#IBAction func numberAction(_ sender: UIButton) {
operatorAC.setTitle("C", for: .normal)
var currentTemp = auxTotalFormatter.string(from: NSNumber(value: temp))!
if !operating && currentTemp.count >= kMaxLength {
return
}
currentTemp = auxFormatter.string(from: NSNumber(value: temp))!
// After selecting an operation
if operating {
total = total == 0 ? temp : total
resultLabel.text = ""
currentTemp = ""
operating = false
}
// After selecting decimal
if decimal {
currentTemp = "\(currentTemp)\(kDecimalSeparator)"
decimal = false
}
if resultLabel.text?.contains(kDecimalSeparator) ?? true {
let number = String(sender.tag-1)
let currentTemp1 = currentTemp.replacingOccurrences(of: ".", with: "", options: .literal, range: nil)
let currentTemp2 = currentTemp1.replacingOccurrences(of: ",", with: ".", options: .literal, range: nil)
temp = Double(currentTemp2 + String(number))!
resultLabel.text = printFormatter.string(from: NSNumber(value: temp))
selectVisualOperation()
sender.shine()
}
else {
let number = String(sender.tag-1)
temp = Double(currentTemp.replacingOccurrences(of: ".", with: "", options: .literal, range: nil) + String(number))!
resultLabel.text = printFormatter.string(from: NSNumber(value: temp))
selectVisualOperation()
sender.shine()
}
}
// Clear
private func clear() {
if operation == .none {
total = 0
}
operation = .none
operatorAC.setTitle("AC", for: .normal)
if temp != 0 {
temp = 0
resultLabel.text = "0"
} else {
total = 0
result()
}
}
`

Let’s assume that you’re doing the traditional calculator style input with buttons for the digits, a decimal separator button and a clear button. The problem with your algorithm is that NumberFormatter with a minimumFractionalDigits of zero will drop trailing digits. So if you try to enter “1.000”, it will say “ok the value is 1, so the string representation of that with zero fraction digits is ‘1’”. The effect of this is that trailing zeros will never appear in the resulting string.
One approach is to adjust minimumFractionalDigits based upon how many fractional digits have been entered thus far. To facilitate that, you need to be able to keep track of how fractional digits have been entered.
There are a number of ways of doing that. One way is through “state management” (e.g. have properties to keep track of whether decimal has been entered already and how many fractional digits have been entered thus far, etc.). To keep it simple, I’m just going to calculate this from the raw user string input:
class ViewController: UIViewController {
let decimalSeparator = Locale.current.decimalSeparator ?? "."
/// The number formatter
///
/// Note, we don't need to set the decimal separator, as it defaults to the current separator.
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
/// The label containing the formatted number
#IBOutlet weak var resultLabel: UILabel!
/// This is the user's raw input, just digits and 0 or one decimal separator, not the formatted number in the label
private var input: String = ""
/// Just add keystroke to `input` string and then format the label.
#IBAction func didTapDigit(_ button: UIButton) {
let digit = ... // determine the numeric value associated with the button that the user tapped; personally I wouldn’t use the `tag` number, but I don’t want to drag us down a tangent, so just do this however you want
addCharacterToInput("\(digit)")
updateLabel()
}
#IBAction func didTapClear(_ sender: UIButton) {
resetInput()
resultLabel.text = "0"
}
/// Only add decimal separator if there's not one there already.
#IBAction func didTapDecimal(_ sender: UIButton) {
if !hasDecimalSeparator() {
addCharacterToInput(".")
}
updateLabel()
}
}
private extension ViewController {
func addCharacterToInput(_ string: String) {
input += String(string)
}
func resetInput() {
input = ""
}
/// How many decimal places in user input.
///
/// - Returns: Returns `nil` if no decimal separator has been entered yet. Otherwise it returns the number of characters after the decimal separator.
func decimalPlaces() -> Int? {
guard let range = input.range(of: decimalSeparator) else {
return nil
}
return input.distance(from: range.upperBound, to: input.endIndex)
}
/// Does the user input include a decimal separator?
/// - Returns: Returns `true` if decimal separator present. Returns `false` if not.
func hasDecimalSeparator() -> Bool {
input.contains(decimalSeparator)
}
/// Update the label on the basis of the `input` string of the raw user input.
func updateLabel() {
let fractionalDigits = decimalPlaces() // figure out decimal places from `input` string
formatter.minimumFractionDigits = fractionalDigits ?? 0 // set formatter accordingly
guard
let value = Double(input), // safely get value from user input ...
var string = formatter.string(for: value) // ...and build base string from that.
else {
resultLabel.text = "Error"
return
}
if fractionalDigits == 0 { // Note, if not `nil` and is zero, that means the user hit decimal separator but has entered no digits yet; we need to manually add decimal separator in output in this scenario
string += decimalSeparator
}
resultLabel.text = string
}
}
That yields:

Related

How to format numbers in a textField with math equation string?

I'm trying to format numbers in a UITextField consists of math equation string: "number + number".
At the moment I can type just a single number, then convert it to Double -> format with NSNumberFormatter -> convert back to String -> assign to textField.text:
The code:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.locale = .current
formatter.roundingMode = .down
let numberString = textField.text ?? ""
guard let range = Range(range, in: numberString) else { return false }
let updatedString = numberString.replacingCharacters(in: range, with: string)
let correctDecimalString = updatedString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "")
guard let value = Double(completeString) else { return false }
let formattedNumber = formatter.string(for: value)
textField.text = formattedNumber
return string == formatter.decimalSeparator
}
Now I want to add a calculation functionality and display a simple math equation in a textField as "number + number", but each number should be formatted as shown above. Example (but without formatting):
I can't properly implement that. The logic for me was: track the String each time new char inserts -> if it has math sign extract numbers -> convert them to Double -> format with NSNumberFormatter -> convert back to String -> construct a new String "number + number".
The code I tried:
if let firstString = completeString.split(separator: "+").first, let secondString = completeString.split(separator: "+").last {
guard let firstValue = Double(firstString) else { return false }
guard let secondValue = Double(secondString) else { return false }
let firstFormattedNumber = formatter.string(for: firstValue)
let secondFormattedNumber = formatter.string(for: secondValue)
textField.text = "\(firstFormattedNumber ?? "") + \(secondFormattedNumber ?? "")"
// another try
if completeString.contains("+") {
let stringArray = completeString.components(separatedBy: "+")
for character in stringArray {
print(character)
guard let value = Double(character) else { return false }
guard let formattedNumber = formatter.string(for: value) else { return false }
textField.text = "\(formattedNumber) + "
}
}
But it's not working properly. I tried to search but didn't find any similar questions.
Test project on GitHub
How can I format the numbers from such a string?
Here is how I was able to solve my question:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.locale = .current
formatter.roundingMode = .down
//set of possible math operations
let symbolsSet = Set(["+","-","x","/"])
let numberString = textField.text ?? ""
guard let range = Range(range, in: numberString) else { return false }
let updatedString = numberString.replacingCharacters(in: range, with: string)
let correctDecimalString = updatedString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "")
//receive math symbol user typed
let symbol = symbolsSet.filter(completeString.contains).first ?? ""
//receive number of symbols in a String. If user wants to type more than one math symbol - do not insert
let amountOfSymbols = completeString.filter({String($0) == symbol}).count
if amountOfSymbols > 1 { return false }
//receive numbers typed by user
let numbersArray = completeString.components(separatedBy: symbol)
//check for each number - if user wants to type more than one decimal sign - do not insert
for number in numbersArray {
let amountOfDecimalSigns = number.filter({$0 == "."}).count
if amountOfDecimalSigns > 1 { return false }
}
guard let firstNumber = Double(String(numbersArray.first ?? "0")) else { return true }
guard let secondNumber = Double(String(numbersArray.last ?? "0")) else { return true }
let firstFormattedNumber = formatter.string(for: firstNumber) ?? ""
let secondFormattedNumber = formatter.string(for: secondNumber) ?? ""
// if user typed math symbol - show 2 numbers and math symbol, if not - show just first typed number
textField.text = completeString.contains(symbol) ? "\(firstFormattedNumber)\(symbol)\(secondFormattedNumber)" : "\(firstFormattedNumber)"
return string == formatter.decimalSeparator
}

How to convert numbers to letters in swift?

I'm stuck help. You have to use strings, but I haven't really found a logical way to make this work well.
var letters = ["Zero","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
var str:String
print("Enter a number from 0 to 10: ")
str = readLine()!
print (letters[0])
...
print("Enter a number from 0 to 10: ")
str = readLine()!
print (letters[10])
Maybe this api is similar but not exactly what you need.
https://developer.apple.com/documentation/foundation/numberformatter/style/spellout
let formatter: NumberFormatter = {
let nf = NumberFormatter()
nf.numberStyle = .spellOut
nf.locale = Locale(identifier: "en_US")
return nf
}()
extension Numeric {
var spelledOut: String? {
return formatter.string(for: self)
}
}
let one = 1.spelledOut
print(one) //->one
print(25.spelledOut) //->twenty-five
print(1.5.spelledOut) //->one point five
https://developer.apple.com/forums/thread/121448
You can do it like this with String extension
extension String {
func wordToInteger() -> Int {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .spellOut
return numberFormatter.number(from: self.lowercased()) as? Int ?? 0
}
}
Use like this
let letters = ["Zero","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
print(letters[10].wordToInteger()) // In your Case
print("Three".wordToInteger())
Output

In Swift 5, How to convert a Float to a String localized in order to display it in a textField?

I need to convert a Float to a localized String.
i write this function which is an extension from Float:
func afficherUnFloat() -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = Locale.current
//numberFormatter.maximumFractionDigits = 2
//numberFormatter.maximumIntegerDigits = 6
if let result = numberFormatter.number(from: self) {
return numberFormatter.string(for: result) ?? "0"
}
return "0"
}
but it didn't work:
Here is the exemple
let xxx : Float = 111.222
myTextField.text = String(xxx).afficherUnFloat()
I have installed a pod KSNumericTextField, that limit the numbers in the textfield. He display it only if it is locally formatted.
When i run the app, it doesn't diplay 111,222 in a french region, or 111,222 in an arabic one.
nothing is dislpayed
Note that there is no need to cast your Float to NSNumber. You can use Formatter's method string(for: Any) instead of NumberFormatter's method string(from: NSNumber). Btw it will create a new number formatter every time you call this property. I would make your formatter static:
extension Formatter {
static let decimal: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = .current
numberFormatter.maximumFractionDigits = 2 // your choice
numberFormatter.maximumIntegerDigits = 6 // your choice
return numberFormatter
}()
}
extension FloatingPoint {
var afficherUnFloat: String { Formatter.decimal.string(for: self) ?? "" }
}
let float: Float = 111.222
let string = float.afficherUnFloat // "111.22"
Here is finaly a solution:
extension Float {
func afficherUnFloat() -> String {
let text : NSNumber = self as NSNumber
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = .current
numberFormatter.groupingSeparator = ""
numberFormatter.maximumFractionDigits = 2 // your choice
numberFormatter.maximumIntegerDigits = 6 // your choice
let result = numberFormatter.string(from: text) ?? ""
return result
}
}
With this, you can format every Float to a localized String, compatible with the keyboard choosen by the user, regardless of his locality or langage.
There is no need to force a special keyboard to have a specific decimal separator.
you can use it like this:
let myFloat: Float = 111.222
let myString :String = myFloat.afficherUnFloat()
myString will be displayed as the location requires

Swift String: append 0s after decimal separator

I need to append 0, 1, or 2 0s to a string, depends on its decimal separator, so that
"100", "100." and "100.0" becomes "100.00"
"100.8" becomes "100.80"
"100.85" remains unchanged
I could find the decimal separator and check its distance to end endIndex of the string, but is there an easier way of doing it?
NumberFormatter does this, but the actual string I have, isn't a plain number that can go through a formatter.
For example:
let amount = "123,456,789"
then formatted amount should be "123,456,789.00"
assumption:
the given string has at most one decimal separator with at most two decimal places
So there can't be string like: "123.4.4.5"
Also I want to use the decimal separator from NumberFormatter().decimalSeparator
You could pass the string through a decimal formatter to get the underlying number, and then back again through the formatter to get a formatted string:
let amount = "123,456,789"
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
let number = formatter.number(from: amount)
let newAmountString = formatter.string(from: number!) //"123,456,789.00"
(You should check that number is not nil before force unwrapping it, with if letor guard)
You could wrap this in a function:
func zeroPadding(toString: String) -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
guard let number = formatter.number(from: toString) else {
return nil
}
return formatter.string(from: number)
}
Here are some test cases:
zeroPadding(toString: "123,456,789") //"123,456,789.00"
zeroPadding(toString: "123,456,789.0") //"123,456,789.00"
zeroPadding(toString: "123,456,789.10") //"123,456,789.10"
zeroPadding(toString: "123,456,789.123") //"123,456,789.12"
zeroPadding(toString: "123.4567") //"123.46"
zeroPadding(toString: "Price: 1€ for a 💩") //nil
Or define it as an extension on String:
extension String {
func withZeroPadding() -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
guard let number = formatter.number(from: self) else {
return nil
}
return formatter.string(from: number)
}
}
And use it like this:
"123.4.4.5".withZeroPadding() //nil
"12.".withZeroPadding() //"12.00"
"123,456,789".withZeroPadding() //"123,456,789.00"
This is the following code snippet I have tested on Playground, it can be achieved more smartly but for now it is working.
//let amount = "123,456,789.545222323"
//let amount = "123,456,789."
let amount = "123,456,789"
let removeSpaces = amount.replacingOccurrences(of: " ", with: "")
if removeSpaces.count > 0
{
let arrSTR = removeSpaces.components(separatedBy: ".")
if arrSTR.count > 1
{
var strAfterDecimal = arrSTR[1]
if strAfterDecimal.count >= 2
{
strAfterDecimal = strAfterDecimal[0..<2]
}else if strAfterDecimal.count != 0
{
strAfterDecimal = "\(strAfterDecimal)0"
}else
{
strAfterDecimal = "00"
}
let finalSTR = String("\(arrSTR[0]).\(strAfterDecimal)")
print("Final with Decimal - \(finalSTR)")
}else
{
let finalSTR = String(arrSTR[0] + ".00")
print("Final without Decimal - \(finalSTR)")
}
}
extension String {
subscript(_ range: CountableRange<Int>) -> String {
let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound))
let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound))
return String(self[idx1..<idx2])
}
}

comma separated number with 2 digit fraction - swift

I have a textfield that it's input is price, so I want to get both like this: 1,111,999.99. I wrote to make it possible but there are two problems. First, after four digits and 2 fraction digit (like 1,234.00) it resets to zero. Second, I can't put fraction in it (fraction is always .00)
how can i make a textfield that receives 1,111,999.99 as input?
in my custom UITextfield:
private var numberFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 0
formatter.numberStyle = .decimal
formatter.decimalSeparator = "."
formatter.groupingSeparator = ","
return formatter
}
var commaValue: String {
return numberFormatter.string(from: value)!
}
var value: NSNumber {
let number = numberFormatter.number(from: self.text ?? "0")
return number!
}
and in my textfieldDidChange method:
#IBAction func textfieldEditingChanged(_ sender: Any) {
let textfield = sender as! UITextField
textfield.text = textfield.commaValue
}
Solved it temporarily this way:
var formattedNumber: String {
guard self.text != "" else {return ""}
var fraction = ""
var digit = ""
let fractionExists = self.text!.contains(".")
let num = self.text?.replacingOccurrences(of: ",", with: "")
let sections = num!.characters.split(separator: ".")
if sections.first != nil
{
let str = String(sections.first!)
let double = Double(str)
guard double != nil else {return self.text ?? ""}
digit = numberFormatter.string(from: NSNumber(value: double ?? 0))!
}
if sections.count > 1
{
fraction = String(sections[1])
if fraction.characters.count > 2
{
fraction = String(fraction.prefix(2))
}
return "\(digit).\(fraction)"
}
if fractionExists
{
return "\(digit)."
}
return digit
}
.
#IBAction func textfieldEditingChanged(_ sender: Any) {
let textfield = sender as! UITextField
textfield.text = textfield.formattedNumber
}