Introduction
To get a currency symbol (like $, €, £) from a currency code (like USD, EUR, GBP) in Swift, use Locale and NumberFormatter from the Foundation framework. The approach is to find a locale that uses the given currency code, then extract its currencySymbol. Since one currency code can map to multiple locales (e.g., USD is used in en_US, es_US, and others), you typically pick the first matching locale or a specific one. NumberFormatter can also format amounts with the correct symbol and placement for any locale.
Basic Currency Symbol Lookup
1import Foundation
2
3func currencySymbol(for code: String) -> String? {
4 let locales = Locale.availableIdentifiers
5 for identifier in locales {
6 let locale = Locale(identifier: identifier)
7 if locale.currency?.identifier == code {
8 return locale.currencySymbol
9 }
10 }
11 return nil
12}
13
14print(currencySymbol(for: "USD") ?? "?") // $
15print(currencySymbol(for: "EUR") ?? "?") // €
16print(currencySymbol(for: "GBP") ?? "?") // £
17print(currencySymbol(for: "JPY") ?? "?") // ¥
18print(currencySymbol(for: "INR") ?? "?") // ₹
Optimized with Dictionary Cache
1import Foundation
2
3class CurrencyHelper {
4 static let shared = CurrencyHelper()
5
6 private let symbolMap: [String: String]
7
8 private init() {
9 var map: [String: String] = [:]
10 for identifier in Locale.availableIdentifiers {
11 let locale = Locale(identifier: identifier)
12 if let code = locale.currency?.identifier,
13 let symbol = locale.currencySymbol,
14 map[code] == nil {
15 map[code] = symbol
16 }
17 }
18 self.symbolMap = map
19 }
20
21 func symbol(for currencyCode: String) -> String {
22 return symbolMap[currencyCode] ?? currencyCode
23 }
24}
25
26// Usage
27print(CurrencyHelper.shared.symbol(for: "USD")) // $
28print(CurrencyHelper.shared.symbol(for: "BRL")) // R$
29print(CurrencyHelper.shared.symbol(for: "KRW")) // ₩
30print(CurrencyHelper.shared.symbol(for: "XYZ")) // XYZ (fallback)
NumberFormatter formats amounts with the correct symbol, placement, and decimal separator:
1import Foundation
2
3func formatCurrency(amount: Double, currencyCode: String,
4 locale: Locale = .current) -> String {
5 let formatter = NumberFormatter()
6 formatter.numberStyle = .currency
7 formatter.currencyCode = currencyCode
8 formatter.locale = locale
9 return formatter.string(from: NSNumber(value: amount)) ?? "\(amount)"
10}
11
12// Format with user's locale
13print(formatCurrency(amount: 1234.56, currencyCode: "USD"))
14// US locale: $1,234.56
15// German locale: 1.234,56 $
16
17// Force a specific locale
18print(formatCurrency(amount: 1234.56, currencyCode: "EUR",
19 locale: Locale(identifier: "de_DE")))
20// 1.234,56 €
21
22print(formatCurrency(amount: 1234.56, currencyCode: "JPY",
23 locale: Locale(identifier: "ja_JP")))
24// ¥1,235 (JPY has no decimal places)
1func currencySymbolViaFormatter(for code: String) -> String {
2 let formatter = NumberFormatter()
3 formatter.numberStyle = .currency
4 formatter.currencyCode = code
5
6 // Format zero, then strip the number and whitespace
7 let formatted = formatter.string(from: 0) ?? code
8 let symbol = formatted.replacingOccurrences(
9 of: "[\\d.,\\s]",
10 with: "",
11 options: .regularExpression
12 )
13 return symbol.isEmpty ? code : symbol
14}
15
16print(currencySymbolViaFormatter(for: "USD")) // $
17print(currencySymbolViaFormatter(for: "GBP")) // £
18print(currencySymbolViaFormatter(for: "THB")) // ฿
Locale-Aware Symbol Display
The same currency code produces different symbols depending on the locale:
1let locales = ["en_US", "en_GB", "de_DE", "ja_JP", "fr_FR"]
2let currencyCode = "USD"
3
4for id in locales {
5 let locale = Locale(identifier: id)
6 let formatter = NumberFormatter()
7 formatter.numberStyle = .currency
8 formatter.currencyCode = currencyCode
9 formatter.locale = locale
10
11 let formatted = formatter.string(from: 1234.56) ?? ""
12 print("\(id): \(formatted)")
13}
14// en_US: $1,234.56
15// en_GB: US$1,234.56
16// de_DE: 1.234,56 $
17// ja_JP: $1,234.56
18// fr_FR: 1 234,56 $US
Note how en_GB uses US$ to disambiguate from £, and fr_FR uses $US with different number formatting.
Common Currency Codes and Symbols
| Code | Symbol | Currency |
| USD |
| EUR | € | Euro |
| GBP | £ | British Pound |
| JPY | ¥ | Japanese Yen |
| CNY | ¥ | Chinese Yuan |
| KRW | ₩ | South Korean Won |
| INR | ₹ | Indian Rupee |
| BRL |
| RUB | ₽ | Russian Ruble |
| THB | ฿ | Thai Baht |
SwiftUI Integration
1import SwiftUI
2
3struct PriceView: View {
4 let amount: Double
5 let currencyCode: String
6
7 var body: some View {
8 Text(formattedPrice)
9 .font(.headline)
10 }
11
12 private var formattedPrice: String {
13 let formatter = NumberFormatter()
14 formatter.numberStyle = .currency
15 formatter.currencyCode = currencyCode
16 formatter.locale = .current
17 return formatter.string(from: NSNumber(value: amount)) ?? "\(amount)"
18 }
19}
20
21struct CurrencyListView: View {
22 let currencies = ["USD", "EUR", "GBP", "JPY", "INR"]
23
24 var body: some View {
25 List(currencies, id: \.self) { code in
26 HStack {
27 Text(CurrencyHelper.shared.symbol(for: code))
28 .font(.title)
29 .frame(width: 50)
30 Text(code)
31 Spacer()
32 PriceView(amount: 99.99, currencyCode: code)
33 }
34 }
35 }
36}
Handling Edge Cases
1// Currencies with no decimal places
2let formatter = NumberFormatter()
3formatter.numberStyle = .currency
4formatter.currencyCode = "JPY"
5print(formatter.string(from: 1000)!) // ¥1,000 (no decimals)
6
7formatter.currencyCode = "KWD" // Kuwaiti Dinar uses 3 decimal places
8print(formatter.string(from: 1.234)!) // KWD 1.234
9
10// Handling nil/unknown currency codes
11func safeSymbol(for code: String?) -> String {
12 guard let code = code, !code.isEmpty else { return "?" }
13 return CurrencyHelper.shared.symbol(for: code)
14}
Common Pitfalls
Iterating all locales on every lookup: Locale.availableIdentifiers contains hundreds of entries. Iterating it for every symbol lookup is slow. Build a dictionary cache once (as shown in CurrencyHelper) and reuse it throughout the app.
Assuming $ always means USD: Many currencies use $ (USD, CAD, AUD, NZD, SGD). In non-US locales, $ may refer to the local dollar. Use the full currency code alongside the symbol for clarity, or let NumberFormatter handle disambiguation.
Hardcoding decimal places for currencies: JPY uses 0 decimal places, KWD uses 3, and most others use 2. NumberFormatter with .currency style handles this automatically. Never format currency amounts with a fixed 2-decimal format.
Ignoring locale differences in symbol placement: Some locales place the symbol before the amount ($100), others after (100 €), and some use different decimal/grouping separators. Always use NumberFormatter for display rather than string concatenation like "$" + amount.
Using Locale.current in unit tests: Locale.current depends on the device/simulator settings and changes between machines. Pass Locale as a parameter in functions you test, and use a fixed locale like Locale(identifier: "en_US") in tests for consistent results.
Summary
Use Locale.availableIdentifiers to find a locale matching a currency code, then access currencySymbol
Cache the code-to-symbol mapping in a dictionary for performance
Use NumberFormatter with .currency style for properly formatted amounts with correct symbols, decimal places, and placement
The same currency code renders differently depending on locale — always use NumberFormatter for user-facing display
Build a CurrencyHelper singleton to provide fast symbol lookups across your app