Давайте предположим, что вы выполняете традиционный ввод в стиле калькулятора с кнопками для цифр, кнопкой десятичного разделителя и кнопкой очистки. Проблема с вашим алгоритмом состоит в том, что NumberFormatter
с minimumFractionalDigits
, равным нулю, сбрасывает завершающие цифры. Поэтому, если вы попытаетесь ввести «1.000», он скажет: «Хорошо, значение равно 1, поэтому строковое представление этого с цифрами нулевой дроби равно« 1 »». Результатом этого является то, что конечные нули никогда не появятся в результирующей строке.
Один из подходов состоит в том, чтобы настроить minimumFractionalDigits
на основе того, сколько дробных цифр уже введено. Чтобы облегчить это, вы должны быть в состоянии отслеживать, как были введены дробные цифры.
Есть несколько способов сделать это. Одним из способов является «управление состоянием» (например, иметь свойства для отслеживания того, был ли уже введен десятичный знак и сколько дробных цифр было введено до настоящего времени и т. Д. c.). Чтобы было проще, я просто собираюсь вычислить это из необработанного ввода строки пользователя:
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
}
}
Это дает: