mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
346 lines
13 KiB
Swift
346 lines
13 KiB
Swift
//
|
|
// CurrencyFormatter.swift
|
|
// CurrencyText
|
|
//
|
|
// Created by Felipe Lefèvre Marino on 1/27/19.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
import TelegramStringFormatting
|
|
|
|
// MARK: - Currency protocols
|
|
|
|
public protocol CurrencyFormatting {
|
|
var maxDigitsCount: Int { get }
|
|
var decimalDigits: Int { get set }
|
|
var maxValue: Double? { get set }
|
|
var minValue: Double? { get set }
|
|
var initialText: String { get }
|
|
var currencySymbol: String { get set }
|
|
|
|
func string(from double: Double) -> String?
|
|
func unformatted(string: String) -> String?
|
|
func double(from string: String) -> Double?
|
|
}
|
|
|
|
public protocol CurrencyAdjusting {
|
|
func formattedStringWithAdjustedDecimalSeparator(from string: String) -> String?
|
|
func formattedStringAdjustedToFitAllowedValues(from string: String) -> String?
|
|
}
|
|
|
|
// MARK: - Currency formatter
|
|
|
|
public class CurrencyFormatter: CurrencyFormatting {
|
|
|
|
/// Set the locale to retrieve the currency from
|
|
/// You can pass a Swift type Locale or one of the
|
|
/// Locales enum options - that encapsulates all available locales.
|
|
public var locale: LocaleConvertible {
|
|
set { self.numberFormatter.locale = newValue.locale }
|
|
get { self.numberFormatter.locale }
|
|
}
|
|
|
|
/// Set the desired currency type
|
|
/// * Note: The currency take effetcs above the displayed currency symbol,
|
|
/// however details such as decimal separators, grouping separators and others
|
|
/// will be set based on the defined locale. So for a precise experience, please
|
|
/// preferarbly setup both, when you are setting a currency that does not match the
|
|
/// default/current user locale.
|
|
public var currency: Currency {
|
|
set { numberFormatter.currencyCode = newValue.rawValue }
|
|
get { Currency(rawValue: numberFormatter.currencyCode) ?? .dollar }
|
|
}
|
|
|
|
/// Define if currency symbol should be presented or not.
|
|
/// Note: when set to false the current currency symbol is removed
|
|
public var showCurrencySymbol: Bool = true {
|
|
didSet {
|
|
numberFormatter.currencySymbol = showCurrencySymbol ? numberFormatter.currencySymbol : ""
|
|
}
|
|
}
|
|
|
|
/// The currency's symbol.
|
|
/// Can be used to read or set a custom symbol.
|
|
/// Note: showCurrencySymbol must be set to true for
|
|
/// the currencySymbol to be correctly changed.
|
|
public var currencySymbol: String {
|
|
set {
|
|
guard showCurrencySymbol else { return }
|
|
numberFormatter.currencySymbol = newValue
|
|
}
|
|
get { numberFormatter.currencySymbol }
|
|
}
|
|
|
|
/// The lowest number allowed as input.
|
|
/// This value is initially set to the text field text
|
|
/// when defined.
|
|
public var minValue: Double? {
|
|
set {
|
|
guard let newValue = newValue else { return }
|
|
numberFormatter.minimum = NSNumber(value: newValue)
|
|
}
|
|
get {
|
|
if let minValue = numberFormatter.minimum {
|
|
return Double(truncating: minValue)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/// The highest number allowed as input.
|
|
/// The text field will not allow the user to increase the input
|
|
/// value beyond it, when defined.
|
|
public var maxValue: Double? {
|
|
set {
|
|
guard let newValue = newValue else { return }
|
|
numberFormatter.maximum = NSNumber(value: newValue)
|
|
}
|
|
get {
|
|
if let maxValue = numberFormatter.maximum {
|
|
return Double(truncating: maxValue)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/// The number of decimal digits shown.
|
|
/// default is set to zero.
|
|
/// * Example: With decimal digits set to 3, if the value to represent is "1",
|
|
/// the formatted text in the fractions will be ",001".
|
|
/// Other than that with the value as 1, the formatted text fractions will be ",1".
|
|
public var decimalDigits: Int {
|
|
set {
|
|
numberFormatter.minimumFractionDigits = newValue
|
|
numberFormatter.maximumFractionDigits = newValue
|
|
}
|
|
get { numberFormatter.minimumFractionDigits }
|
|
}
|
|
|
|
/// Set decimal numbers behavior.
|
|
/// When set to true decimalDigits are automatically set to 2 (most currencies pattern),
|
|
/// and the decimal separator is presented. Otherwise decimal digits are not shown and
|
|
/// the separator gets hidden as well
|
|
/// When reading it returns the current pattern based on the setup.
|
|
/// Note: Setting decimal digits after, or alwaysShowsDecimalSeparator can overlap this definitios,
|
|
/// and should be only done if you need specific cases
|
|
public var hasDecimals: Bool {
|
|
set {
|
|
self.decimalDigits = newValue ? 2 : 0
|
|
self.numberFormatter.alwaysShowsDecimalSeparator = newValue ? true : false
|
|
}
|
|
get { decimalDigits != 0 }
|
|
}
|
|
|
|
/// Defines the string that is the decimal separator
|
|
/// Note: only presented when hasDecimals is true OR decimalDigits
|
|
/// is greater than 0.
|
|
public var decimalSeparator: String {
|
|
set { self.numberFormatter.currencyDecimalSeparator = newValue }
|
|
get { numberFormatter.currencyDecimalSeparator }
|
|
}
|
|
|
|
/// Can be used to set a custom currency code string
|
|
public var currencyCode: String {
|
|
set { self.numberFormatter.currencyCode = newValue }
|
|
get { numberFormatter.currencyCode }
|
|
}
|
|
|
|
/// Sets if decimal separator should always be presented,
|
|
/// even when decimal digits are disabled
|
|
public var alwaysShowsDecimalSeparator: Bool {
|
|
set { self.numberFormatter.alwaysShowsDecimalSeparator = newValue }
|
|
get { numberFormatter.alwaysShowsDecimalSeparator }
|
|
}
|
|
|
|
/// The amount of grouped numbers. This definition is fixed for at least
|
|
/// the first non-decimal group of numbers, and is applied to all other
|
|
/// groups if secondaryGroupingSize does not have another value.
|
|
public var groupingSize: Int {
|
|
set { self.numberFormatter.groupingSize = newValue }
|
|
get { numberFormatter.groupingSize }
|
|
}
|
|
|
|
/// The amount of grouped numbers after the first group.
|
|
/// Example: for the given value of 99999999999, when grouping size
|
|
/// is set to 3 and secondaryGroupingSize has 4 as value,
|
|
/// the number is represented as: (9999) (9999) [999].
|
|
/// Beign [] grouping size and () secondary grouping size.
|
|
public var secondaryGroupingSize: Int {
|
|
set { self.numberFormatter.secondaryGroupingSize = newValue }
|
|
get { numberFormatter.secondaryGroupingSize }
|
|
}
|
|
|
|
/// Defines the string that is shown between groups of numbers
|
|
/// * Example: a monetary value of a thousand (1000) with a grouping
|
|
/// separator == "." is represented as `1.000` *.
|
|
/// Note: It automatically sets hasGroupingSeparator to true.
|
|
public var groupingSeparator: String {
|
|
set {
|
|
self.numberFormatter.currencyGroupingSeparator = newValue
|
|
self.numberFormatter.usesGroupingSeparator = true
|
|
}
|
|
get { self.numberFormatter.currencyGroupingSeparator }
|
|
}
|
|
|
|
/// Sets if has separator between all group of numbers.
|
|
/// * Example: when set to false, a bug number such as a million
|
|
/// is represented by tight numbers "1000000". Otherwise if set
|
|
/// to true each group is separated by the defined `groupingSeparator`. *
|
|
/// Note: When set to true only works by defining a grouping separator.
|
|
public var hasGroupingSeparator: Bool {
|
|
set { self.numberFormatter.usesGroupingSeparator = newValue }
|
|
get { self.numberFormatter.usesGroupingSeparator }
|
|
}
|
|
|
|
/// Value that will be presented when the text field
|
|
/// text values matches zero (0)
|
|
public var zeroSymbol: String? {
|
|
set { numberFormatter.zeroSymbol = newValue }
|
|
get { numberFormatter.zeroSymbol }
|
|
}
|
|
|
|
/// Value that will be presented when the text field
|
|
/// is empty. The default is "" - empty string
|
|
public var nilSymbol: String {
|
|
set { numberFormatter.nilSymbol = newValue }
|
|
get { return numberFormatter.nilSymbol }
|
|
}
|
|
|
|
/// Encapsulated Number formatter
|
|
let numberFormatter: NumberFormatter
|
|
|
|
/// Maximum allowed number of integers
|
|
public var maxIntegers: Int? {
|
|
set {
|
|
guard let maxIntegers = newValue else { return }
|
|
numberFormatter.maximumIntegerDigits = maxIntegers
|
|
}
|
|
get { return numberFormatter.maximumIntegerDigits }
|
|
}
|
|
|
|
/// Returns the maximum allowed number of numerical characters
|
|
public var maxDigitsCount: Int {
|
|
numberFormatter.maximumIntegerDigits + numberFormatter.maximumFractionDigits
|
|
}
|
|
|
|
/// The value zero formatted to serve as initial text.
|
|
public var initialText: String {
|
|
numberFormatter.string(from: 0) ?? "0.0"
|
|
}
|
|
|
|
//MARK: - INIT
|
|
|
|
/// Handler to initialize a new style.
|
|
public typealias InitHandler = ((CurrencyFormatter) -> (Void))
|
|
|
|
/// Initialize a new currency formatter with optional configuration handler callback.
|
|
///
|
|
/// - Parameter handler: configuration handler callback.
|
|
|
|
public init(currency: String, _ handler: InitHandler? = nil) {
|
|
numberFormatter = setupCurrencyNumberFormatter(currency: currency)
|
|
|
|
numberFormatter.alwaysShowsDecimalSeparator = false
|
|
/*numberFormatter.numberStyle = .currency
|
|
|
|
numberFormatter.minimumFractionDigits = 2
|
|
numberFormatter.maximumFractionDigits = 2
|
|
numberFormatter.minimumIntegerDigits = 1*/
|
|
|
|
handler?(self)
|
|
}
|
|
}
|
|
|
|
// MARK: Format
|
|
extension CurrencyFormatter {
|
|
|
|
/// Returns a currency string from a given double value.
|
|
///
|
|
/// - Parameter double: the monetary amount.
|
|
/// - Returns: formatted currency string.
|
|
public func string(from double: Double) -> String? {
|
|
let validValue = valueAdjustedToFitAllowedValues(from: double)
|
|
return numberFormatter.string(from: validValue)
|
|
}
|
|
|
|
/// Returns a double from a string that represents a numerical value.
|
|
///
|
|
/// - Parameter string: string that describes the numerical value.
|
|
/// - Returns: the value as a Double.
|
|
public func double(from string: String) -> Double? {
|
|
Double(string)
|
|
}
|
|
|
|
/// Receives a currency formatted string and returns its
|
|
/// numerical/unformatted representation.
|
|
///
|
|
/// - Parameter string: currency formatted string
|
|
/// - Returns: numerical representation
|
|
public func unformatted(string: String) -> String? {
|
|
string.numeralFormat()
|
|
}
|
|
}
|
|
|
|
// MARK: - Currency adjusting conformance
|
|
|
|
extension CurrencyFormatter: CurrencyAdjusting {
|
|
|
|
/// Receives a currency formatted String, and returns it with its decimal separator adjusted.
|
|
///
|
|
/// _Note_: Useful when appending values to a currency formatted String.
|
|
/// E.g. "$ 23.24" after users taps an additional number, is equal = "$ 23.247".
|
|
/// Which gets updated to "$ 232.47".
|
|
///
|
|
/// - Parameter string: The currency formatted String
|
|
/// - Returns: The currency formatted received String with its decimal separator adjusted
|
|
public func formattedStringWithAdjustedDecimalSeparator(from string: String) -> String? {
|
|
let adjustedString = numeralStringWithAdjustedDecimalSeparator(from: string)
|
|
guard let value = double(from: adjustedString) else { return nil }
|
|
|
|
return self.numberFormatter.string(from: value)
|
|
}
|
|
|
|
/// Receives a currency formatted String, and returns it to fit the formatter's min and max values, when needed.
|
|
///
|
|
/// - Parameter string: The currency formatted String
|
|
/// - Returns: The currency formatted String, or the formatted version of its closes allowed value, min or max, depending on the closest boundary.
|
|
public func formattedStringAdjustedToFitAllowedValues(from string: String) -> String? {
|
|
let adjustedString = numeralStringWithAdjustedDecimalSeparator(from: string)
|
|
guard let originalValue = double(from: adjustedString) else { return nil }
|
|
|
|
return self.string(from: originalValue)
|
|
}
|
|
|
|
/// Receives a currency formatted String, and returns a numeral version of it with its decimal separator adjusted.
|
|
///
|
|
/// E.g. "$ 23.24", after users taps an additional number, get equal as "$ 23.247". The returned value would be "232.47".
|
|
///
|
|
/// - Parameter string: The currency formatted String
|
|
/// - Returns: The received String with numeral format and with its decimal separator adjusted
|
|
private func numeralStringWithAdjustedDecimalSeparator(from string: String) -> String {
|
|
var updatedString = string.numeralFormat()
|
|
let isNegative: Bool = string.contains(String.negativeSymbol)
|
|
|
|
updatedString = isNegative ? .negativeSymbol + updatedString : updatedString
|
|
updatedString.updateDecimalSeparator(decimalDigits: decimalDigits)
|
|
|
|
return updatedString
|
|
}
|
|
|
|
/// Receives a Double value, and returns it adjusted to fit min and max allowed values, when needed.
|
|
/// If the value respect number formatter's min and max, it will be returned without changes.
|
|
///
|
|
/// - Parameter value: The value to be adjusted if needed
|
|
/// - Returns: The value updated or not, depending on the formatter's settings
|
|
private func valueAdjustedToFitAllowedValues(from value: Double) -> Double {
|
|
if let minValue = minValue, value < minValue {
|
|
return minValue
|
|
} else if let maxValue = maxValue, value > maxValue {
|
|
return maxValue
|
|
}
|
|
|
|
return value
|
|
}
|
|
}
|