Thus far, we’ve learned how to build an app using data that we’ve manually entered into our app. However, there is a world of data out there begging to be used in apps of your creation! All we have to do is learn how to access it and integrate in our app, so we’re going to dive in to the expansive world of API. We’ll start small. Gotta catch ’em all! It’ll make more sense in a bit, but I had to do it for the rhyme. 😬
In simple terms, an API (Application Programming Interface) allows your app to communicate with a service to get data from the internet. For this homework, we’re going to be using the PokeAPI, a free public API that contains, you guessed it, Pokémon! This fun API has all the information you’d ever need on Pokémon, but for this exercise, we’re going to keep it simple. We’ll request a list of Pokémon names and display them in a simple SwiftUI list. Ready? Let’s get to it.
- Create a new project and name it BeginnerPokedex or Homework9. Whichever works! As per usual, make sure the language is Swift and the user interface is SwiftUI.
- Let’s take a look at our API. As mentioned, we’ll be using PokeAPI which you can find here: https://pokeapi.co/api/v2/pokemon Go ahead and click on it and take a look. By default, it will probably look like a glob of data. This data format is called JSON (JavaScript Object Notation). By default, browsers don’t format the data as intended. However, there are several fantastic plugins/extensions for Chrome and Safari that will beautify this json to make it more readable. You can also ask ChatGPT to format it for you. 🙂 Nevertheless, after properly formatting with line breaks and indentation, JSON should look something like this:
"count": 1302, "next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20", "previous": null, "results": [ { "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }, { "name": "ivysaur", "url": "https://pokeapi.co/api/v2/pokemon/2/" }, { "name": "venusaur", "url": "https://pokeapi.co/api/v2/pokemon/3/" }, { ...
Much better, right? So our goal for this app is to fetch this data and display the list of Pokémon names in our app. In order to make JSON work in Swift, we need to decode it into Swift friendly data structures. Fortunately, that’s where our friend, Codable, comes in. More on that in a bit. For now, let’s build our data model…
- A data model is essentially a “model” of how your data will be defined and organized in your app. In other words, we need to create a model that represents each Pokémon in our code. To do this, we’ll create a struct to hold the Pokémon’s name. Let’s add the following code in ContentView right after
import SwiftUI
:
struct Pokemon: Codable, Identifiable { let id = UUID() let name: String } struct PokemonResponse: Codable { let results: [Pokemon] }
There are several bits of code here that likely look unfamiliar, so let’s break it down…
- struct Pokemon: Like other structs we’ve worked with this creates a Pokémon structure or object with 2 properties:
- id: A unique identifier which is required by SwiftUI to display items in a list. More on this below (UUID).
- name: The name of the Pokémon.
- Codable: This bit of magic allows us to convert JSON data from the API into this struct. For more info, check out Chapter 21 in our textbook. It dives deeper into these concepts and then some.
- Identifiable: This is needed so SwiftUI can uniquely identify each Pokémon when displaying them in a list.
- UUID: Many API’s provide a unique ID for each item listed. However, many don’t, and PokeAPI is one of those many. Since the API doesn’t give us a unique ID for each Pokémon, UUID() generates a unique ID for us.
- struct PokemonResponse: Lastly, this represents the response from the API, which contains a results array of Pokémon.
- Now that we’ve set up our data model, we need to create what’s called a ViewModel, a model responsible for managing the data that the view needs to display. In other words, our ViewModel will fetch the data from the API and store the Pokémon in a list to be displayed in our SwiftUI. To do this, add the following code below our
PokemonResponse
struct:
class PokemonViewModel: ObservableObject { @Published var pokemonList: [Pokemon] = [] func fetchPokemon() { guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon?limit=20") else { print("Invalid URL") return } URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { print("Error fetching data: \(error)") return } guard let data = data else { print("No data received") return } do { let decodedResponse = try JSONDecoder().decode(PokemonResponse.self, from: data) // Update the UI on the main thread DispatchQueue.main.async { self.pokemonList = decodedResponse.results } } catch { print("Error decoding JSON: \(error)") } }.resume() // Start the data task } }
I know, I know. It looks like a lot, but it’s not as bad as you think. Let’s talk about it.
- PokemonViewModel: This is the class responsible for managing the data.
- @Published var pokemonList: This stores the list of Pokémon we fetch. The @Published keyword tells SwiftUI to update the UI when this list changes.
- fetchPokemon(): This function makes the API call and decodes the JSON response.
- URLSession.shared.dataTask: This starts the network request to fetch data from the API.
- JSONDecoder(): This awesome bit of code tells our app to decode the raw JSON into our PokemonResponse model.
- DispatchQueue.main.async: Since the network call happens in the background, we update the UI on the main thread.
Finally! We have everything we need! Let’s display the data in a list. Replace the default filler code (body variable + VStack inside) in the ContentView struct with the following code:
@StateObject var viewModel = PokemonViewModel() var body: some View { NavigationView { List(viewModel.pokemonList) { pokemon in Text(pokemon.name.capitalized) } .navigationTitle("Pokémon List") .onAppear { viewModel.fetchPokemon() } } }
Some of this probably looks a bit more familiar, but here’s the breakdown:
- @StateObject var viewModel: We create an instance of PokemonViewModel that will manage the Pokémon data.
- List(viewModel.pokemonList): This should look familiar from Homework 8. This List view displays the Pokémon in a scrollable list. Each item in the list corresponds to a Pokémon in the pokemonList array.
- Text(pokemon.name.capitalized): For each Pokémon in the list, we display its name. Fun fact: Appending
capitalized
capitalizes the first letter. - .navigationTitle: You could have used a VStack and add a title above our list, but why not learn another fun modifier for NavigationViews! Now you know how to add a nav title!
- onAppear: This triggers the fetchPokemon() function when the view appears on the screen, ensuring that the data is fetched as soon as the app runs.
Do you see a list of Pokémon on your canvas/simulator? Congrats! You now know how to pull data from an API and display it in your app! Well sort of. If this felt like a lot to remember, good news. You’re not expected to, especially at this point! That’s why we have documentation and in our case this lesson and textbook for reference. There’s soooo much to learn, and honestly, it would require much, much longer than one class to learn it all at a professional level. Instead, familiarize yourself with the process. Try and understand what is happening with the code, and if you’re feeling crazy, find another API to play around with! If you can learn to harness the power of APIs (even while referencing this material or other documentation as needed), your app development possibilities become endless! Nice work!💪
Fun Fact: Want to view more than 20 Pokémon in your list? Easy! Simply change the “limit” from your API URL string in Step 4. E.g. “https://pokeapi.co/api/v2/pokemon?limit=60” will display 60 Pokémon instead of 20!