Coding Workshop 8 - Location Finder App Part 1

homepage

 

We are going to build a Location Finder application where you can select a country and enter a postal or zip code to find the location in that country based on that postal code and plot it on a map.

Let's start building this app by creating a new iOS App and name it Location Finder.

image-20230117094417899

Rename ContentView

As we did in the previous example, rename ContentView to better reflect what the view represents. Our application is actually only going to have this single view.

  1. Rename ContentView as LocationFinderView.

  2. Update the name of the PreviewProvider struct to reflect this change as well.

Exploring the Zippopotam API

An API stands for Application Programming Interface. It is a set of tools and protocols which allow two different software programs to communicate with each other. In simple terms, it is a way for different software components to talk to each other and share data.

An example of an API is the Mastodon API. It allows developers to access Mastodon data, such as posts or toots, profiles, and trends, and to use it in their own applications. They can use the API to create apps that show a user's posts, or to search for posts related to certain topics.

Many APIs require that you have an API key to access the data. This means that your access key can be revoked as it was recently for some Twitter client applications which then prevented the applications from working at all.

It is more common now to find that if you want to build an application to present information from a source on the Internet, that source provides and API. This includes APIs for weather, stock market and literally anything you can think of. Some are free and some have limitations as to how often you can make calls to retrieve data from them.

If you want to get your hands dirty and craft your own applications, I suggest you visit the apipheny website that has a list of 90 of the top APIs in different categories. Some required keys, but they list 15 that do not, and we are going to be using one of them.

https://apipheny.io/free-api/

The Zippopotam project is for finding locations based on Postal Codes or Zip Codes. It is a free API and the response you get is in the form of JSON, which we saw in the previous tutorial. There are over 60 countries supported and it is an open source project.

Using the API

Using the API is simple and you can test in the browser you form a URL as follows:

  1. Start with the base URL - https://api.zippopotam.us.
  2. Add a path that will represent the country code for which you are searching - ex /CA for Canada.
  3. Add a path that will represent the full or part of the Postal or Zip code in the range that the country supports as shown in their chart. Example /V5M.
  4. The final URL then is something like https://api.zippopotam.us/CA/V5M.

If I enter this into a Web browser and go to that URL, you get a JSON Response.

Creating the Model

If you recall from the previous application, JSON is made up of key - value pairs where the keys will represent the names of our properties that we will define for our model, and the values will be the values used in a particular instance of that object.

So let's start there by creating a model that can reflect this response, It is a little more tricky than the previous one in that it appears we have 4 properties, but one of them is an array of other objects.

  1. Copy the JSON to your clipboard so that you have it available.

  2. In Xcode create a new Swift file and call it Location

    1. Paste in the copied code then select it and surround the code with /*at the top and */at the bottom. This will create the entire block as a comment.

    2. Create a new struct and name it Location and make sure that it conforms to the Codable protocol.

      What we want to do is pick out the keys that we want to use in our application and create properties in our Location struct.

    3. There is no need for us to use the returned postal code as we will be typing it in so we know what it is.

      There is no rule that states that you have to use every key value pair that an API has to offer

    4. We will be able to use country though, so we can specify that as a String property.

    5. Similarly, we will not need the country abbreviation as we will have that already when we make our submission.

    6. The next property an array of objects so we need to create another struct that will represent that array of objects so we can create an embedded struct called Place for that object.

    1. This struct has key value pairs that I am going to use in my application.

      1. There is a problem with place name however as property names in a Swift struct must not contain spaces. So, let's replace that by using camelCase and name the property placeName and it will be of type String.

      2. I will also need the other remaining properties except for state abbreviation and all are String.

      3. Now that we have that struct inside our Location struct, we can use it and create a places property in our Location struct that is an array of those Place objects.

      4. We still need to resolve the issue of our renaming that place name to placeName and we do that by creating a CodingKey enum inside the Place struct that conforms to both the String and CodingKey protocols.

      5. Here you need to create a case that represents all of your properties and only assign a String value for those properties that have been changed, ie placeName = "place name".

LocationFinderView

We can now start to design our user interface in LocationFinderView.

  1. Remove the Image view and its modifiers from the VStack.

  2. Remove the .padding.

  3. Embed the VStack in a NavigationStack.

  4. Add a .navigationTitle to the VStack and provide the string "Location Finder".

Countries JSON

As the first item in the VStack we want to create a Picker that will list all of the countries available.

I am going to make this easy for you and provide you with another JSON file that is structured in such a way that we can build an array of objects that have two properties; the country's name, code and range. These are 3 of the columns for the list provide on the web site.

image-20230115145626461

What I did to create this JSON file that I call countries.json was to copy the table to my clipboard and pasted it into a Numbers document.

I then removed the columns that I did not want and saved it as a CSV file.

Next, I used a web site called https://www.convertcsv.com/csv-to-json.htm and used it to convert the vile to JSON.

This file is available in the downloadable LocationFinder Assets folder.

  1. Drag the countries.json file from the download Assets folder and drop it right in the navigation pane of your application. Anything store in this location is accessible to your app and it is known as the app's Bundle.

2023-01-15_15-03-10

App Icon and Image Asset

While we are here, open the Xcode assets folder and notice that right now, our app has no icon, and we will also need the image for our main screen when our app loads and before we choose a country.

You should have two assets available to you.

appstore1024.png

This is an image that is 1024 X 1024 and I want to use it as the App Icon.

  1. Open the assets folder in Xcode.
  2. Select the AppIcon item and drag and drop the image on to the placeholder in the center.

locationFinder.png

This is a a smaller version with rounded corners and is 256 X 256.

2023-01-16_08-51-09

Country Model

Well, now that we have our JSON, we will need to create a Model into which we can populate our array of countries.

The model will correspond to the JSON in the countries.json file.

This is fairly straightforward. Our model will have to have 3 properties; name, code and range and all are String types.

  1. Create a new Swift file and call it Country.

  2. Inside this file, create a new struct with the same name but make sure that it conforms to both the Codable and Hashable protocols.

    Codable conformance is so that we can load from the JSON file and Hashable because we want to use it in a Picker selection.

  3. Create the 3 properties as discussed above.

  4. This will allow us to decode our JSON and populate an array of Country - [Country].

There is one more instance that I want I my array however, and that is a static Country instance called none that we can use as the first element of the array so that when we present our picker, the selection will say "Select Country" and the country code will be "XX".

  1. Inside the Country struct create this static instance.

    Since the property is a static one for the struct Country, we can reference it as Country.none.

LocationService

In the last tutorial we created a DataStore class that we used to manage our data and functions related to that data. In this tutorial we are going to create a similar class but this time we are going to call it LocationService.

  1. Create a new Swift file and name it LocationService.

  2. Inside that class, create a new class using the same name and make sure the it conforms to the ObservableObject protocol so that we can create Published properties that can be observed by other views and update the state and UI of our application.

  3. Create a @Published property for countries that is an array of Country and initialize as an empty array.

Loading the countries.json

  1. Inside the class, create a new function called loadCountries()and it is in here that we will load and decode the json file and assign the decoded array to our published properties.

  2. We must first ensure that the file exists in our application Bundle and if not, we cannot proceed so we use a guard check to unwrap the url or produce a fatalError.

  3. Now that we have ensured that the file exists, we can try to get the data from the contents of that url. If it fails again, there is no point in proceeding so we use another guard check to unwrap the data and produce a fatalError.

  4. Next, we can use a JSONDecoder class's decode method to try and decode from that data our array of Country, This may fail, so we will enclose the try in a do...catch block and if it does fail, again, no point in proceeding, so we will catch the error and produce a fatalError.

  5. If the decoding try is successful, we can assign it to our @Published countries property as it will be our array.

  6. Then, we can insert our static Country.none at the beginning our the array using an array insert method to position it at the beginning of the array; at index 0.

Initializer

With our loadCountries function complete, and our countries array populated by that function, we can initiate a call to this function when our class is instantiated.

LocationFinderView

In the previous tutorial, we inject our ObservableObject class into the environment at the app entry point because we wanted to use the data in multiple SwiftUI views. We will only have a single SwiftUI view in this app, so we do not need to do that here.

  1. We can create our instance of the LocationService class right inside the LocationFinderView struct and we do this as a @StateObject

LocationPicker

Since we are instantiating an instance of this class now, our LocationService class has been initialized and its countries property has been populated from the countries.json file. We can use this array then to create a Picker.

  1. We will need a @State property to bind our selection to so create one and provide the static Country.none value as the initial value.
  1. Replace the Text view with a Picker view using the title key, selection, label constructor.

    1. For the titleKey use the string "Select Country"
    2. For the selection, bind it to $selectedCountry.
  2. For the content (the trailing closure) we can use a ForEach loop over our locationService.countries array. This needs to be an identifiable collection but we have not specified that our Country object conforms to the Identifiable protocol and provided it with an id property. We can bypass that conformance so long as we can specify an id keyPath for one of the properties that will be unique, and in our case that will be the code property,

    This will provide us with an iterator variable that we can call country that we can use in creating our picker selection views. We can use the country.name as the string for a Text view, but since we are only displaying the country's name, we must provide a tag for that selection to indicate that we want to use the entire country to bind to our selectedCountry.

  3. Add a .buttonStyle of .bordered to make the picker stand out more.

  4. Add a Spacer() to push the picker up to the top of the VStack.

    2023-01-16_09-49-15

Back to Project Index