Created
December 30, 2021 05:00
-
-
Save nesimtunc/95c64554553860c7e8f9506ff8b2243e to your computer and use it in GitHub Desktop.
Swift Custom Number Formatter for Currency
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
struct CurrencyFormatter { | |
let currencySymbol: String | |
let customLocale: String | |
private var customNumberFormatter: NumberFormatter { | |
let formatter = NumberFormatter() | |
formatter.locale = Locale.init(identifier: customLocale) | |
formatter.usesGroupingSeparator = true | |
formatter.numberStyle = .currency | |
formatter.currencySymbol = currencySymbol | |
return formatter | |
} | |
/// Locale's currency symbol still can be nil | |
/// therefore as a default you it will use $ | |
init() { | |
self.currencySymbol = NSLocale.current.currencySymbol ?? "$" | |
self.customLocale = NSLocale.current.languageCode ?? "en_US" | |
} | |
/// You can init with custom locale and currency symbol | |
init(withCustomLocale locale: String, withCurrency currencySymbol: String) { | |
self.currencySymbol = currencySymbol | |
self.customLocale = locale | |
} | |
func makeAttributedCurrency(_ amount: Decimal) -> NSMutableAttributedString { | |
let tuple = breakIntoDecimalAndFraction(amount) | |
return makeBalanceAttributed(currency: tuple.0, fraction: tuple.1) | |
} | |
// Converts 929466.23 > "929,466" "23" | |
func breakIntoDecimalAndFraction(_ amount: Decimal) -> (String, String) { | |
let tuple = modf(amount.doubleValue) | |
let decimal = convertCurrency(withDecimal: tuple.0) | |
let fraction = convertFraction(tuple.1) | |
return (decimal, fraction) | |
} | |
// Converts 929466 > 929,466 | |
private func convertCurrency(withDecimal decimalPart: Double) -> String { | |
let currencyWithDecimal = formatCurrency(decimalPart) // "$929,466.00" | |
let decimalSeparator = customNumberFormatter.decimalSeparator! // "." | |
let decimalParts = currencyWithDecimal.components(separatedBy: decimalSeparator) // "$929,466" "00" | |
var currency = decimalParts.first! // "$929,466" | |
currency.removeFirst() // "929,466" | |
return currency | |
} | |
// Convert 0.23 > 23 | |
private func convertFraction(_ fractionPart: Double) -> String { | |
let fraction: String | |
if fractionPart == 0 { | |
fraction = "00" | |
} else { | |
fraction = String(format: "%.0f", fractionPart * 100) | |
} | |
return fraction | |
} | |
// Converts 929466 > $929,466.00 | |
func formatCurrency(_ currency: Double) -> String { | |
if let result = customNumberFormatter.string(from: NSNumber(value: currency)) { | |
return result | |
} | |
return "" | |
} | |
private func makeBalanceAttributed(currency: String, fraction: String) -> NSMutableAttributedString { | |
let currencySymbolAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.preferredFont(forTextStyle: .callout), .baselineOffset: 8] | |
let currencyAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.preferredFont(forTextStyle: .title1)] | |
let fractionAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.preferredFont(forTextStyle: .callout), .baselineOffset: 8] | |
let rootString = NSMutableAttributedString(string: self.currencySymbol, attributes: currencySymbolAttributes) | |
let currencyString = NSAttributedString(string: currency, attributes: currencyAttributes) | |
let fractionString = NSAttributedString(string: fraction, attributes: fractionAttributes) | |
rootString.append(currencyString) | |
rootString.append(fractionString) | |
return rootString | |
} | |
} | |
// MARK: Unit Tests | |
import Foundation | |
import XCTest | |
@testable import Banky | |
class CurrencyFormatterTests: XCTestCase { | |
var formatter: CurrencyFormatter! | |
let customCurrency = "$" | |
let customLocale = "en_US" | |
override func setUp() { | |
super.setUp() | |
formatter = CurrencyFormatter(withCustomLocale: customLocale, withCurrency: customCurrency) | |
} | |
func testBreakCurrencyIntoPenny() throws { | |
let result = formatter.breakIntoDecimalAndFraction(929466.23) | |
let currencyWithDecimal = result.0 | |
let fraction = result.1 | |
XCTAssertEqual(currencyWithDecimal, "929,466") | |
XCTAssertEqual(fraction, "23") | |
} | |
func testCurrencyFormatted() throws { | |
let result = formatter.formatCurrency(929466.23) | |
XCTAssertEqual(result, "\(customCurrency)929,466.23") | |
} | |
func testZeroCurrencyFormatted() throws { | |
let result = formatter.formatCurrency(0) | |
XCTAssertEqual(result, "\(customCurrency)0.00") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment