Creating an iOS Currency Exchange Rate Widget: A Step-by-Step Guide
Tags: swift, ios, widget
Published: 2023-05-08 18:46:29

{% youtube 5GBqLbmOkA4 %}

## Introduction:

Widgets are a powerful way to provide users with a glanceable view of your app's information right on their iPhone's Home Screen or Today View. With iOS 14 and later, you can create your own custom widgets to provide useful information at a glance. In this tutorial, we'll walk you through creating a currency exchange rate widget that fetches data from an API and displays it on the widget. We'll be using the endpoint from PrivatBank to retrieve exchange rates for USD and EUR against UAH.

## Prerequisites:

- Xcode (latest version)
- macOS with the latest version of SwiftUI
- Basic understanding of SwiftUI and Swift programming language
- An active Apple Developer account

Let's get started!

1\. Create a new Xcode project:

   Open Xcode and create a new project using the "App" template under "iOS." Give your project a name, such as "Private Exchange," and choose "SwiftUI" as the Interface and "Swift" as the Language. Set the minimum deployment target to iOS 14 or later, as widgets are not supported in earlier versions.

2\. Add a new Widget Extension:

   In the Xcode Project Navigator, click on "File" > "New" > "Target." Choose the "Widget Extension" template and click "Next." Give your widget extension a name, such as "PrivateExchangeWidget," and make sure the "Language" is set to "Swift." Click "Finish" to create the widget extension.

3\. Create a data model for the currency rates:

   In the widget extension folder, create a new Swift file called "CurrencyRate.swift" and define a `CurrencyRate` struct that conforms to the `Decodable` protocol. This struct will represent the currency rate data fetched from the API.

   ```swift
   struct CurrencyRate: Decodable {
       let ccy: String
       let base_ccy: String
       let buy: String
       let sale: String
   }
   ```

4\. Create a data fetcher to fetch currency rates from the API:

   In the same folder, create another Swift file called "CurrencyRateFetcher.swift" and define a `CurrencyRateFetcher` class with a shared singleton instance. This class will be responsible for fetching currency rates from the API.

   ```swift
   import Foundation

   class CurrencyRateFetcher {
       static let shared = CurrencyRateFetcher()
       private let apiUrl = "https://api.privatbank.ua/p24api/pubinfo?exchange&coursid=5"

       func fetchRates(completion: @escaping ([CurrencyRate]?) -> Void) {
           guard let url = URL(string: apiUrl) else {
               completion(nil)
               return
           }

           let task = URLSession.shared.dataTask(with: url) { data, response, error in
               guard let data = data, error == nil else {
                   completion(nil)
                   return
               }

               let decoder = JSONDecoder()
               let currencyRates = try? decoder.decode([CurrencyRate].self, from: data)
               completion(currencyRates)
           }

           task.resume()
       }
   }
   ```

5\. Modify the widget's timeline provider to fetch data (continued):

   Update the `getTimeline(for:in:completion:)` method to fetch currency rates from the API and pass them to the `SimpleEntry`.

   ```swift
   func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
       CurrencyRateFetcher.shared.fetchRates { currencyRates in
           let currentDate = Date()
           let entry = SimpleEntry(date: currentDate, currencyRates: currencyRates)
           let refreshDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)!
           let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
           completion(timeline)
       }
   }
   ```

   This code fetches the currency rates and creates a timeline entry with the current date and fetched rates. The timeline policy is set to `.after(refreshDate)`, which will refresh the data every hour.

6\. Update the widget's view to display currency rates:

   Open the main Swift file for the widget (e.g., "Private_Exchange.swift") and modify the `Private_ExchangeEntryView` to display the currency rates in a SwiftUI view.

   ```swift
   struct Private_ExchangeEntryView : View {
       var entry: Provider.Entry

       private func currencySymbol(for code: String) -> String {
           switch code {
           case "EUR":
               return "€"
           case "USD":
               return "$"
           case "UAH":
               return "₴"
           default:
               return code
           }
       }

       var body: some View {
           VStack {
               if let currencyRates = entry.currencyRates {
                   ForEach(currencyRates, id: \.ccy) { rate in
                       HStack {
                           Text("\(currencySymbol(for: rate.ccy))")
                           Spacer()
                           Text("\(Double(rate.buy)?.rounded(toPlaces: 2) ?? 0) / \(Double(rate.sale)?.rounded(toPlaces: 2) ?? 0)")
                       }
                   }
               } else {
                   Text("Loading...")
               }
           }
           .padding()
       }
   }
   ```

   This code defines a `currencySymbol(for:)` function that returns the appropriate currency symbol for a given currency code. The view displays a list of currency rates using a `VStack` and `ForEach`, or a "Loading..." text if the rates are not yet available.

7\. Test the widget:

   Build and run the project on an iOS simulator or a physical device to see the currency exchange rate widget in action. You can add the widget to the Home Screen or Today View by following the standard process for adding widgets on iOS.

## Conclusion:

In this tutorial, we've walked you through creating a custom currency exchange rate widget for iOS using SwiftUI and an API. You can now expand on this basic implementation to add more functionality, such as user-configurable currency pairs or different display styles for the widget. Happy coding!