Example Project

The SwiftUI currency converter example

Our next SwiftUI example is the currency conversion app. This is an app with two views embedded in a Navigation Controller. The first view displays the currency calculator:

The second view displays a list of currencies for the user to select.

Displaying the list of currencies

The general rule in SwiftUI is that we make a separate View struct for each view in our app, and put each view's code in its own separate source code file. Here is the code in the CurrencyList.swift file:

import SwiftUI

struct CurrencyList : View {
    var currencies : [String]
    @Binding var selected : String

    var body : some View {
        List {
            ForEach(currencies,id: \.self) { currency in
                Text(currency).onTapGesture {
                    self.selected = currency
                }.listRowBackground(self.selected == currency ? Color.accentColor : Color(UIColor.systemGroupedBackground))
            }
        }
    }
}

Since we are going to be displaying a list of currency strings, we will need a currencies property that will store that list of strings. Here we are also going to need a second property that will store what currency the user has selected from the list. This property uses the @Binding property modifier to indicate that this property is actually a reference to a property that we are going to get from somewhere else. Specifically, this will be a reference to a property that comes over from the first view when we navigate to this second view.

To implement a table view in SwiftUI we use the List() component. The closure that we pass to that component's initializer uses the SwiftUI ForEach() construct to specify how to make a cell in the table view for each element of the data list the list is based on. The first parameter to the ForEach initializer is a list of data items that we want to iterate over: in this case the list of currency strings. The trailing closure that the ForEach uses is a closure that takes the data for a particular item as its parameter and constructs a view from it. In this case we make a text label that displays the currency string. We also equip the label with an onTapGesture() event handler that sets the currently selected currency when the user taps on the label.

The navigation controller and the first view

Here now is the code for the app's first view, which implements the calculator:

struct ContentView: View {
    @FocusState private var amountIsFocused: Bool
    @State var current : String
    @State private var amount : String = "0.00"
    @State private var convertedAmount : String = "0.00"
    let rates = ["EUR":0.9829,"GBP":0.8397,"JPY":135.72,"CAD":1.3049]
    var keys : Array<String> {
        return Array(rates.keys)
    }

    func convert() -> String {
        let dollars : Double = Double(amount) ?? 0.0
        let amt = dollars * (rates[current] ?? 0.0)
        return String(format: "%.2f",amt)
    }

    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    TextField("",text:$amount)
                        .keyboardType(.decimalPad)
                        .textFieldStyle(.roundedBorder)
                        .focused($amountIsFocused)
                    Text("USD")
                }.padding()
                Button("Convert") {
                    convertedAmount = convert()
                    amountIsFocused = false
                }.buttonStyle(.bordered).padding()
                Text("\(convertedAmount) \(current)").padding()
            }
            .onAppear() { convertedAmount = convert() }
            .navigationTitle("Travel Money")
            .navigationBarTitleDisplayMode(.inline)
            .navigationBarItems(
                  trailing: NavigationLink("Currency") {
                          CurrencyList(currencies:keys,selected:$current)
                     }
                  )
        }
    }
}

The view we construct here is a NavigationView that contains the app's first view as its only content. That first view is implemented in the usual way as a VStack.

Note that we have to apply a series of modifiers to the view to set up all of its features, including the details of the navigation bar. The most important modifier here is navigationBarItems(), which is where we set up the NavigationLink that implements the navigation to the second view. NavigationLinks can actually appear anywhere in a view, and act very much like links in a web page. The most important aspect of the NavigationLink is its destination, which is a view that will be constructed and shown when the user clicks the navigation link. In this case, we want to navigate to the view that displays the list of currencies, so we set the destination as a new CurrencyList view. Note that the second parameter to the CurrencyList initializer is a reference to our own current property, which stores the currency code for the currency we are currently working with. When the second view changes that value it will automatically be updating our property, since the second view will be working with a reference to our property.

Since we are going to be navigating between views in the app, we may need to use the equivalent of the viewWillAppear() or viewWillDisappear() methods we used in earlier examples. In SwiftUI we implement the equivalent of viewWillAppear() by adding an onAppear() modifier to the view. In this app we are displaying a converted amount. Since the user may navigate to the second view and select a different currency there, we need to have to add an onAppear() modifier to our first view that recomputes the converted amount when we navigate back from the second view to the first.