Apoorv Blog

Fetch Exchange Rate from Free JSON API in iOS Swift

Updated on:



Click here to see tutorial on sorting sections by date

Screenshot Tutorial

1. First let’s setup the XCode project as usual. Create new single view application X code project. Set project name to exchange rate and save it to the desktop.

Set project name

2. Go to main storyboard and select view controller and delete it. Add Table View Controller in the main storyboard

Add table view controller

3. Select table view controller and make it Initial View Controller.

set initial view controller

4. Select table view controller and go to Editor -> Embed in -> Navigation Controller. I will set a date as title here later.

Add navigation controller

5. Select prototype cell. Set it’s identifier to rates.

set prototype cell identifier

6. Delete view controller swift file and add new cocoa touch class file of subclass UITableViewController and name it RatesTableViewController.

rates swift file

7. First thing you do after adding file is to set custom class for ToDoListTableViewController.

set custom class

8. Add another swift file with name Networking.swift to store networking code away from view controller.

networking swift file

9. First we are going to start adding code in networking swift file. We need to create model for json object for decoding using decodable.

 9
10
11
12
13
14
15
16
17
18
import Foundation

struct rates: Decodable {
    
    var date: String
    
    var base: String
    
    var rates: [String: Double]
}

10. Just below the model create enum for networking request. There are only two cases if request failed then failure and if request succeeded then we send the decoded model along with success case.

17
18
19
20
21
22
23
24
25
    var rates: [String: Double]
}

enum Result {
    
    case failure
    
    case success(rates)
}

11. Just below result enum create string variable to hold api url.

24
25
26
27
    case success(rates)
}

let urlString = "https://api.exchangeratesapi.io/latest"

12. Just below string url create function to get latest exchange rate. It will have escaping closure that takes in result enum and returns void. Then convert url string into url if that fails then pass failure in completion and return.

27
28
29
30
31
let urlString = "https://api.exchangeratesapi.io/latest"

func getLatest(completion: @escaping (Result) -> Void) {
    
    guard let url = URL(string: urlString) else { completion(.failure); return  }

13. Using unwrapped url create network request with url session. Create shared session data task with completion handler.

31
32
33
    guard let url = URL(string: urlString) else { completion(.failure); return  }
    
    URLSession.shared.dataTask(with: url) { (data, response, error) in

14. In the completion block first check if error is nil. Convert response into http url response and verify status code is 200. Also wrap optional data otherwise pass failure in completion and return.

33
34
35
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        
        guard error == nil, let urlResponse = response as? HTTPURLResponse, urlResponse.statusCode == 200, let data = data else { completion(.failure); return }

15. In the do block convert data into rates model using json decoder. If that fails then catch error and pass in failure in completion. If decoding is successful then pass in decoded value with success in completion. And most importantly don’t forget to call resume at the end otherwise the network request will not start.

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func getLatest(completion: @escaping (Result) -> Void) {
    
    guard let url = URL(string: urlString) else { completion(.failure); return  }
    
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        
        guard error == nil, let urlResponse = response as? HTTPURLResponse, urlResponse.statusCode == 200, let data = data else { completion(.failure); return }
        
        do {
            
            let exchangeRates = try JSONDecoder().decode(rates.self, from: data)
            
            completion(.success(exchangeRates))
        }
        catch { completion(.failure) }
        
        }.resume()
}

16. Create an empty array of string to hold all row values for tableview.

11
12
13
class RatesTableViewController: UITableViewController {
    
    var exchangeRates = [String]()

17. In the view did load set table’s footer view to an empty view.

15
16
17
18
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.tableFooterView = UIView()

18. Call the function from networking file and in completion block create weak reference to table view. Now all networking operations happen in the background queue and you cannot update ui from the background queue. That is why add code into the main queue asynchronously.

18
19
20
21
22
        tableView.tableFooterView = UIView()
        
        getLatest { [weak self] (result) in
            
            DispatchQueue.main.async {

19. Inside main queue switch the result and if the result is sucess then unwrap the attached response. Now you don’t have to empty the exchange rates array because it’s empty to begin with. But I am emptying array to safeguard if I move code into view did appear. Then set table view controller’s title to date string from response.

22
23
24
25
26
27
28
29
30
            DispatchQueue.main.async {
                
                switch result {
                    
                case .success(let response):
                    
                    self?.exchangeRates.removeAll(keepingCapacity: false)
                    
                    self?.title = "Updated on " + response.date

20. From the response rate dictionary get all the keys and sort them alphabetically. Then go through each key from keys array. Get value for each key. Then append rate into the exchange rate array. You can get base currency using response base property. Also in case of result is failure then just print error. Here you can also show an alert if you want.

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.tableFooterView = UIView()
        
        getLatest { [weak self] (result) in
            
            DispatchQueue.main.async {
                
                switch result {
                    
                case .success(let response):
                    
                    self?.exchangeRates.removeAll(keepingCapacity: false)
                    
                    self?.title = "Updated on " + response.date
                    
                    let keys = response.rates.keys.sorted()
                    
                    for key in keys {
                        
                        guard let value = response.rates[key] else { continue }
                        
                        self?.exchangeRates.append("1 \(response.base) = \(value) \(key)")
                    }
                    
                    self?.tableView.reloadData()
                    
                case .failure: print("error")
                }
            }
        }
        
        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem
    }

21. Set number of sections to 1.

55
56
57
58
59
60
    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

22. Set number of rows in sections to exchange rate array count.

62
63
64
65
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return exchangeRates.count
    }

23. Uncomment cell for row method and set identifier to rates. And set cells label text to exchange rate at index path dot row.

67
68
69
70
71
72
73
74
75
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "rates", for: indexPath)

        // Configure the cell...

        cell.textLabel?.text = exchangeRates[indexPath.row]
        
        return cell
    }

24. Finally build and run on iPhone XR.

demo exchange rate

25. Challenge: - fetch exchange rate for USD currency and fetch exchange rate for only few currencies.

demo exchange rate

comments powered by Disqus