WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Day 19 Questions

Forums > 100 Days of SwiftUI

Hi All,

Here is my solution to day 19, there were two things I was trying to do but I couldnt crack it... Any help would be appreciated

Firstly, changing the currency on the 'To' changes the currency symble fine, but not on the From field. (It just stays as £)

Secondly instead on hardcoding the conversion rates in to the calculation I wanted so store them in a datadictionary - something like this

let convertFromRates = ["GBP": 1, "USD" : 0.81, "EUR": 0.54, "INR" :0.01]

But Im not sure if this is possible?

Thanks!

struct ContentView: View {
  @State private var fromAmount = 0.0
  @State private var convertFromRate = "GBP"
  @State private var convertToRate = "EUR"
  @FocusState private var fromCurrencyIsFocused: Bool
  let convertFromRates = ["GBP", "USD", "EUR", "INR"]
  let convertToRates = ["GBP", "USD", "EUR", "INR"]
  var convertToGBPRate: Double {
    if (convertFromRate == "GBP") {
      return 1
    } else if (convertFromRate == "EUR") {
      return 0.85416915
    } else if (convertFromRate == "USD") {
      return 0.81053798
    } else if (convertFromRate == "INR") {
      return 0.0104991
    }
    else {
      return 0
    }
  }
  var convertFromGBPRate: Double {
    if (convertToRate == "GBP") {
      return 1
    } else if (convertToRate == "EUR") {
      return 1.17073
    } else if (convertToRate == "USD") {
      return 1.23375
    } else if (convertToRate == "INR") {
      return 95.245973
    }
    else {
      return 0
    }
  }
  var convertedAmount: Double {
    let convToGBP:Double = fromAmount * convertToGBPRate
    let convFromGBP:Double = convToGBP * convertFromGBPRate
    return convFromGBP
  }
  var body: some View {
    NavigationView {
      Form {
        Section {
          TextField("Amount", value: $fromAmount, format: .currency(code: convertFromRate))
        } header: { Text("From Amount:")}
          .keyboardType(.decimalPad)
          .focused($fromCurrencyIsFocused)
        Section {
          Picker("Rate", selection: $convertFromRate) {
            ForEach(convertFromRates, id: \.self)
            {
              Text($0)
            }
          }
          .pickerStyle(.segmented)
        } header: { Text("From Currency") }
        Section {
          Picker("Rate", selection: $convertToRate) {
            ForEach(convertToRates, id: \.self)
            {
              Text($0)
            }
          }
          .pickerStyle(.segmented)
        }header: { Text("To Currency") }
        Section {
          Text(convertedAmount, format: .currency(code: convertToRate))
        } header: { Text("To Amount:")}
      }
      .navigationTitle("Currency Convertor")
      .toolbar {
        ToolbarItemGroup(placement: .keyboard)
        {
          Spacer()
          Button("Done")
          {
            fromCurrencyIsFocused = false
          }
        }
      }
    }
  }
}
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

   

Greenis is asking about dictionaries:

Secondly instead of hardcoding the conversion rates into the calculation
I wanted so store them in a dictionary - something like this
let convertFromRates = ... snip....
But Im not sure if this is possible?

Sure! It's possible, and I think you did it. Paste this code into Playgrounds. (Please tell us you're using Playgrounds, yes?)

// Paste into Playgrounds
var exchangeRateFor = ["GBP" : 1, "USD": 0.81, "EUR": 054, "IRN": 0.01]  // Inferred types.
exchangeRateFor["USD"]  // Like a paper dictionary. Look up this "term" and return its definition.
exchangeRateFor["JPY"]  // What happens when you look for Yen?

Looking for Yen returns nil. So you see the return type is an Optional.

Iterating over Dictionary Values

Here's a snip to loop over dictionary values. Paste into Playgrounds.

for (countryCode, exchangeRatio) in exchangeRateFor {
    print("Currency: \(countryCode) exchange ratio: \(exchangeRatio)")
}

Reduce your Code

Very clever looking to dictionaries for this solution! Nice! You'll clean up lots of ugly, hard-to-maintain code.

// Goodbye and good riddance!
var convertToGBPRate: Double {
    if (convertFromRate == "GBP") {
      return 1
    } else if (convertFromRate == "EUR") {
      return 0.85416915
    } else if (convertFromRate == "USD") {
      return 0.81053798
    } else if (convertFromRate == "INR") {
      return 0.0104991
    }
    else {
      return 0
    }

Variable Name

I changed the dictionary name to exchangeRateFor.

To me, it might make more sense in context when I'm thumbing through a dictionary looking, uh, well, looking for the exchange rate for a certain country code.

Excellent questions! Keep coding!

   

Wow! Thanks for your detailed reply :)

Can your exchangeRateFor be bound to a picker?

I will play with what you suggest this evening

Thanks!

   

More questions from @greenis

Can your exchangeRateFor be bound to a picker?

Do you want the answer? or do you want to learn?

I provided you with code to iterate over the dictionary. How might you extract those results into a computed var then use those values to bind to a picker? Or (spoiler alert) you can look in the developer documentation and learn about dictionary keys.

Try it out in Playgrounds, and please return here to share your code!

See-> Dictionary Keys

   

Great thanks!

I have cracked it - its much neater now

import SwiftUI

struct ContentView: View {
    @State private var fromAmount = 0.0
    @State private var convertFromRate = "GBP"
    @State private var convertToRate = "EUR"
    @FocusState private var fromCurrencyIsFocused: Bool

    let convertPickerRates = ["GBP", "USD", "EUR", "INR"]
    let exchangeRateFor = ["GBP": 1, "USD": 0.81053798, "EUR": 0.85416915, "INR": 0.0104991]

    var convertToGBPRate: Double {
        return exchangeRateFor[convertFromRate, default:0]
    }

    var convertFromGBPRate: Double {
        return exchangeRateFor[convertToRate, default:0]
    }

    var convertedAmount: Double {
        let newConv:Double = (fromAmount / convertToGBPRate) * convertFromGBPRate
        return newConv

    }

    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Amount", value: $fromAmount, format: .currency(code: convertFromRate))
                } header: { Text("From Amount:")}
                    .keyboardType(.decimalPad)
                    .focused($fromCurrencyIsFocused)

                Section {
                    Picker("Rate", selection: $convertFromRate) {
                        ForEach(convertPickerRates, id: \.self)
                        {
                            Text($0)
                        }
                    }
                    .pickerStyle(.segmented)
                } header: { Text("From Currency") }

                Section {
                    Picker("Rate", selection: $convertToRate) {
                        ForEach(convertPickerRates, id: \.self)
                        {
                            Text($0)
                        }
                    }
                    .pickerStyle(.segmented)

                }header: { Text("To Currency") }
                Section {
                    Text(convertedAmount, format: .currency(code: convertToRate))
                } header: { Text("To Amount:")}
            }
            .navigationTitle("Currency Convertor")
            .toolbar {
                ToolbarItemGroup(placement: .keyboard)
                {
                    Spacer()
                    Button("Done")
                    {
                        fromCurrencyIsFocused = false
                    }
                }
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Looking forward to the next challenge!

   

Well done! Here are a couple of pointers.

Pointer #1

var convertedAmount: Double {
    let newConv:Double = (fromAmount / convertToGBPRate) * convertFromGBPRate
    return newConv
}

When you compute a let variable and then use it once, consider instead just substituting the computation for the variable! Then when you reduce this code to one line, you don't need the return statement. It's very Swifty to keep your code compact.

// instead consider this
var convertedAmount: Double {
    (fromAmount / convertToGBPRate) * convertFromGBPRate  // reduce this to one line!
}

Pointer #2

ForEach(convertPickerRates, id: \.self)
    {
        Text($0)
    }

Here you're building a number of Text views to create the list of currencies for your picker. This required you to create a separate array of currencies, called convertPickerRates. So if you add a new currencey to the exchangeRateFor dictionary, you'll also have to update this array. Too much work! Instead, consider just having a dictionary. Then use the power of dictionaries to create your pick list!

Try this:

// Paste into Playgrounds.
import SwiftUI
import PlaygroundSupport
// Keep ONE array of currencies and exchange rates
// Add a new exchange rate here, both the currencies AND picker are updated! Cool!
let exchangeRateFor = ["GBP": 1, "USD": 0.81053798, "EUR": 0.85416915, "INR": 0.0104991]

// Calculate the list of available currencies, and sort!
var currencies: [String] {
    Array(exchangeRateFor.keys).sorted(by: <) // <-- Note the sort!
}

struct ExchangeRateView: View {
    @State private var convertFromRate = 0 // default
    var body: some View {
        Picker("Rate", selection: $convertFromRate) {
            // Build a Text() view for each currency in your exchangeRateFor dictionary
            ForEach(currencies, id: \.self)  { Text($0) }
        }.pickerStyle(.segmented)
    }
}
PlaygroundPage.current.setLiveView( ExchangeRateView().padding() )  // <-- Run this line in Playgrounds.

   

Thanks for tips here, very helpful :)

I tried binding exchangeRateFor directly to the picker just looking to the .keys but couldnt get it to work, I never thought to create a new array with just these!

Thanks for the tips, I really appreciate it.

I have just been coding vb.net applications for the last several years at work and getting very bored of coding, swift has get me excited about it again!

   

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.