├── MIT-License ├── README.md └── CurrencyConverter.swift /MIT-License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 InfiniteScrollingBackground 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CurrencyConverter (Swift 5.0) 2 | A simple currency converter written in Swift 5.
3 | The exchange rates are fetched from the following XML file: https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml
4 | This Currency Converter is used in the Objective Calculator app. Check it out: http://itunes.apple.com/app/id1287000522
5 | 6 | **How to use:**
7 | • Add the "CurrencyConverter.swift" file to your project
8 | • Create a `CurrencyConverter` instance
9 | • Call the `updateExchangeRates` method
10 | • After it updates the exchange rates, call the `convert` method
11 | • That's it!
12 | 13 | # Example 14 | ```swift 15 | class ViewController: UIViewController { 16 | 17 | // Creates the Currency Converter instance: 18 | let currencyConverter = CurrencyConverter() 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | // Updates the exchange rates: 24 | currencyConverter.updateExchangeRates(completion: { 25 | // The code inside here runs after all the data is fetched. 26 | 27 | // Now you can convert any currency: 28 | // • Example_1 (USD to EUR): 29 | let doubleResult = self.currencyConverter.convert(10, valueCurrency: .USD, outputCurrency: .EUR) 30 | print("••• 10 USD = \(doubleResult!) EUR •••") 31 | 32 | // • Example_2 (EUR to GBP) - Returning a formatted String: 33 | let formattedResult = self.currencyConverter.convertAndFormat(10, valueCurrency: .EUR, outputCurrency: .GBP, numberStyle: .decimal, decimalPlaces: 4) 34 | print("••• Formatted Result (10 EUR to GBP): \(formattedResult!) •••") 35 | }) 36 | } 37 | 38 | } 39 | ``` 40 | 41 | # List of currently supported currencies: 42 | USD US dollar
43 | JPY Japanese yen
44 | BGN Bulgarian lev
45 | CZK Czech koruna
46 | DKK Danish krone
47 | GBP Pound sterling
48 | HUF Hungarian forint
49 | PLN Polish zloty
50 | RON Romanian leu
51 | SEK Swedish krona
52 | CHF Swiss franc
53 | ISK Icelandic krona
54 | NOK Norwegian krone
55 | HRK Croatian kuna
56 | RUB Russian rouble
57 | TRY Turkish lira
58 | AUD Australian dollar
59 | BRL Brazilian real
60 | CAD Canadian dollar
61 | CNY Chinese yuan renminbi
62 | HKD Hong Kong dollar
63 | IDR Indonesian rupiah
64 | ILS Israeli shekel
65 | INR Indian rupee
66 | KRW South Korean won
67 | MXN Mexican peso
68 | MYR Malaysian ringgit
69 | NZD New Zealand dollar
70 | PHP Philippine peso
71 | SGD Singapore dollar
72 | THB Thai baht
73 | ZAR South African rand 74 | 75 | # License 76 | InfiniteScrollingBackground project is licensed under MIT License ([MIT-License](MIT-License) or https://opensource.org/licenses/MIT) 77 | -------------------------------------------------------------------------------- /CurrencyConverter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrencyConverter.swift 3 | // Created by Thiago Martins on 26/03/19. 4 | // 5 | 6 | import Foundation 7 | 8 | // Global Enumerations: 9 | enum Currency : String, CaseIterable { 10 | 11 | case AUD = "AUD"; case INR = "INR"; case TRY = "TRY" 12 | case BGN = "BGN"; case ISK = "ISK"; case USD = "USD" 13 | case BRL = "BRL"; case JPY = "JPY"; case ZAR = "ZAR" 14 | case CAD = "CAD"; case KRW = "KRW" 15 | case CHF = "CHF"; case MXN = "MXN" 16 | case CNY = "CNY"; case MYR = "MYR" 17 | case CZK = "CZK"; case NOK = "NOK" 18 | case DKK = "DKK"; case NZD = "NZD" 19 | case EUR = "EUR"; case PHP = "PHP" 20 | case GBP = "GBP"; case PLN = "PLN" 21 | case HKD = "HKD"; case RON = "RON" 22 | case HRK = "HRK"; case RUB = "RUB" 23 | case HUF = "HUF"; case SEK = "SEK" 24 | case IDR = "IDR"; case SGD = "SGD" 25 | case ILS = "ILS"; case THB = "THB" 26 | 27 | // Public Static Methods: 28 | /** Returns a currency name with it's flag (🇺🇸 USD, for example). */ 29 | static func nameWithFlag(for currency : Currency) -> String { 30 | return (Currency.flagsByCurrencies[currency] ?? "?") + " " + currency.rawValue 31 | } 32 | 33 | // Public Properties: 34 | /** Returns an array with all currency names and their respective flags. */ 35 | static let allNamesWithFlags : [String] = { 36 | var namesWithFlags : [String] = [] 37 | for currency in Currency.allCases { 38 | namesWithFlags.append(Currency.nameWithFlag(for: currency)) 39 | } 40 | return namesWithFlags 41 | }() 42 | 43 | static let flagsByCurrencies : [Currency : String] = [ 44 | .AUD : "🇦🇺", .INR : "🇮🇳", .TRY : "🇹🇷", 45 | .BGN : "🇧🇬", .ISK : "🇮🇸", .USD : "🇺🇸", 46 | .BRL : "🇧🇷", .JPY : "🇯🇵", .ZAR : "🇿🇦", 47 | .CAD : "🇨🇦", .KRW : "🇰🇷", 48 | .CHF : "🇨🇭", .MXN : "🇲🇽", 49 | .CNY : "🇨🇳", .MYR : "🇲🇾", 50 | .CZK : "🇨🇿", .NOK : "🇳🇴", 51 | .DKK : "🇩🇰", .NZD : "🇳🇿", 52 | .EUR : "🇪🇺", .PHP : "🇵🇭", 53 | .GBP : "🇬🇧", .PLN : "🇵🇱", 54 | .HKD : "🇭🇰", .RON : "🇷🇴", 55 | .HRK : "🇭🇷", .RUB : "🇷🇺", 56 | .HUF : "🇭🇺", .SEK : "🇸🇪", 57 | .IDR : "🇮🇩", .SGD : "🇸🇬", 58 | .ILS : "🇮🇱", .THB : "🇹🇭", 59 | ] 60 | } 61 | 62 | // Global Classes: 63 | class CurrencyConverter { 64 | 65 | // Private Properties: 66 | private var exchangeRates : [Currency : Double] = [:] 67 | private let xmlParser = CurrencyXMLParser() 68 | 69 | // Initialization: 70 | init() { updateExchangeRates {} } 71 | 72 | // Public Methods: 73 | /** Updates the exchange rate and runs the completion afterwards. */ 74 | public func updateExchangeRates(completion : @escaping () -> Void = {}) { 75 | xmlParser.parse(completion: { 76 | // Gets the exchange rate from the internet: 77 | self.exchangeRates = self.xmlParser.getExchangeRates() 78 | // Saves the updated exchange rate to the device's local storage: 79 | CurrencyConverterLocalData.saveMostRecentExchangeRates(self.exchangeRates) 80 | // Runs the completion: 81 | completion() 82 | }, errorCompletion: { // No internet access/network error: 83 | // Loads the most recent exchange rate from the device's local storage: 84 | self.exchangeRates = CurrencyConverterLocalData.loadMostRecentExchangeRates() 85 | // Runs the completion: 86 | completion() 87 | }) 88 | } 89 | 90 | /** 91 | Converts a Double value based on it's currency (valueCurrency) and the output currency (outputCurrency). 92 | USD to EUR conversion example: convert(42, valueCurrency: .USD, outputCurrency: .EUR) 93 | */ 94 | public func convert(_ value : Double, valueCurrency : Currency, outputCurrency : Currency) -> Double? { 95 | guard let valueRate = exchangeRates[valueCurrency] else { return nil } 96 | guard let outputRate = exchangeRates[outputCurrency] else { return nil } 97 | let multiplier = outputRate/valueRate 98 | return value * multiplier 99 | } 100 | 101 | /** 102 | Converts a Double value based on it's currency and the output currency, and returns a formatted String. 103 | Usage example: convertAndFormat(42, valueCurrency: .USD, outputCurrency: .EUR, numberStyle: .currency, decimalPlaces: 4) 104 | */ 105 | public func convertAndFormat(_ value : Double, valueCurrency : Currency, outputCurrency : Currency, numberStyle : NumberFormatter.Style, decimalPlaces : Int) -> String? { 106 | guard let doubleOutput = convert(value, valueCurrency: valueCurrency, outputCurrency: outputCurrency) else { 107 | return nil 108 | } 109 | return format(doubleOutput, numberStyle: numberStyle, decimalPlaces: decimalPlaces) 110 | } 111 | 112 | /** 113 | Returns a formatted string from a double value. 114 | Usage example: format(42, numberStyle: .currency, decimalPlaces: 4) 115 | */ 116 | public func format(_ value : Double, numberStyle : NumberFormatter.Style, decimalPlaces : Int) -> String? { 117 | let formatter = NumberFormatter() 118 | formatter.numberStyle = numberStyle 119 | formatter.maximumFractionDigits = decimalPlaces 120 | return formatter.string(from: NSNumber(value: value)) 121 | } 122 | 123 | } 124 | 125 | // Private Classes: 126 | private class CurrencyXMLParser : NSObject, XMLParserDelegate { 127 | 128 | // Private Properties: 129 | private let xmlURL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml" 130 | private var exchangeRates : [Currency : Double] = [ 131 | .EUR : 1.0 // Base currency 132 | ] 133 | 134 | // Public Methods: 135 | public func getExchangeRates() -> [Currency : Double] { 136 | return exchangeRates 137 | } 138 | 139 | public func parse(completion : @escaping () -> Void, errorCompletion : @escaping () -> Void) { 140 | guard let url = URL(string: xmlURL) else { return } 141 | let task = URLSession.shared.dataTask(with: url) { data, response, error in 142 | guard let data = data, error == nil else { 143 | print("Failed to parse the XML!") 144 | print(error ?? "Unknown error") 145 | errorCompletion() 146 | return 147 | } 148 | let parser = XMLParser(data: data) 149 | parser.delegate = self 150 | if parser.parse() { 151 | completion() 152 | } else { 153 | errorCompletion() 154 | } 155 | } 156 | task.resume() 157 | } 158 | 159 | // Private Methods: 160 | private func makeExchangeRate(currency : String, rate : String) -> (currency : Currency, rate : Double)? { 161 | guard let currency = Currency(rawValue: currency) else { return nil } 162 | guard let rate = Double(rate) else { return nil } 163 | return (currency, rate) 164 | } 165 | 166 | // XML Parse Methods (from XMLParserDelegate): 167 | func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { 168 | if elementName == "Cube"{ 169 | guard let currency = attributeDict["currency"] else { return } 170 | guard let rate = attributeDict["rate"] else { return } 171 | guard let exchangeRate = makeExchangeRate(currency: currency, rate: rate) else { return } 172 | exchangeRates.updateValue(exchangeRate.rate, forKey: exchangeRate.currency) 173 | } 174 | } 175 | 176 | } 177 | 178 | // Private Classes: 179 | private class CurrencyConverterLocalData { 180 | 181 | // Structs: 182 | struct Keys { 183 | static let mostRecentExchangeRates = "CurrencyConverterLocalData.Keys.mostRecentExchangeRates" 184 | } 185 | 186 | // Static Properties: 187 | // • This will never be used once the method CurrencyConverter.updateExchangeRates is called with internet access. 188 | // • This is just an emergency callback, in case the user doesn't have internet access the first time running the app. 189 | // Updated in: 04/15/2019. 190 | static let fallBackExchangeRates : [Currency : Double] = [ 191 | .USD : 1.1321, 192 | .JPY : 126.76, 193 | .BGN : 1.9558, 194 | .CZK : 25.623, 195 | .DKK : 7.4643, 196 | .GBP : 0.86290, 197 | .HUF : 321.90, 198 | .PLN : 4.2796, 199 | .RON : 4.7598, 200 | .SEK : 10.4788, 201 | .CHF : 1.1326, 202 | .ISK : 135.20, 203 | .NOK : 9.6020, 204 | .HRK : 7.4350, 205 | .RUB : 72.6133, 206 | .TRY : 6.5350, 207 | .AUD : 1.5771, 208 | .BRL : 4.3884, 209 | .CAD : 1.5082, 210 | .CNY : 7.5939, 211 | .HKD : 8.8788, 212 | .IDR : 15954.12, 213 | .ILS : 4.0389, 214 | .INR : 78.2915, 215 | .KRW : 1283.00, 216 | .MXN : 21.2360, 217 | .MYR : 4.6580, 218 | .NZD : 1.6748, 219 | .PHP : 58.553, 220 | .SGD : 1.5318, 221 | .THB : 35.955, 222 | .ZAR : 15.7631, 223 | ] 224 | 225 | // Static Methods: 226 | /** Saves the most recent exchange rates by locally storing it. */ 227 | static func saveMostRecentExchangeRates(_ exchangeRates : [Currency : Double]) { 228 | let convertedExchangeRates = convertExchangeRatesForUserDefaults(exchangeRates) 229 | UserDefaults.standard.set(convertedExchangeRates, forKey: Keys.mostRecentExchangeRates) 230 | } 231 | 232 | /** Loads the most recent exchange rates from the local storage. */ 233 | static func loadMostRecentExchangeRates() -> [Currency : Double] { 234 | if let userDefaultsExchangeRates = UserDefaults.standard.dictionary(forKey: Keys.mostRecentExchangeRates) as? [String : Double] { 235 | return convertExchangeRatesFromUserDefaults(userDefaultsExchangeRates) 236 | } else { 237 | // Fallback: 238 | return fallBackExchangeRates 239 | } 240 | } 241 | 242 | // Private Static Methods: 243 | /** Converts the [String : Double] dictionary with the exchange rates to a [Currency : Double] dictionary. */ 244 | private static func convertExchangeRatesFromUserDefaults(_ userDefaultsExchangeRates : [String : Double]) -> [Currency : Double] { 245 | var exchangeRates : [Currency : Double] = [:] 246 | for userDefaultExchangeRate in userDefaultsExchangeRates { 247 | if let currency = Currency(rawValue: userDefaultExchangeRate.key) { 248 | exchangeRates.updateValue(userDefaultExchangeRate.value, forKey: currency) 249 | } 250 | } 251 | return exchangeRates 252 | } 253 | 254 | /** 255 | Converts the [Currency : Double] dictionary with the exchange rates to a [String : Double] one so it can be stored locally. 256 | */ 257 | private static func convertExchangeRatesForUserDefaults(_ exchangeRates : [Currency : Double]) -> [String : Double] { 258 | var userDefaultsExchangeRates : [String : Double] = [:] 259 | for exchangeRate in exchangeRates { 260 | userDefaultsExchangeRates.updateValue(exchangeRate.value, forKey: exchangeRate.key.rawValue) 261 | } 262 | return userDefaultsExchangeRates 263 | } 264 | 265 | } 266 | --------------------------------------------------------------------------------