In this lab, you'll build the first part of an app that will allow users to view Yelp restaurants. You'll work in collaborative pairs--Pair Programming--to apply the skills you've learned so far building your Flix App Assignment. Just like the Flix App, you'll create a network request to fetch data from the web, however instead of movies, you'll be getting blog restaurant data from the Yelp API.
The checkpoints below should be implemented as pairs. In pair programming, there are two roles: navigator and driver.
- How to Collaborate in Labs - Pair Programming Video: Check out this short video before getting started with the lab.
- Navigator: Makes the decision on what step to do next. Their job is to describe the step using high level language ("Let's print out something when the user is scrolling"). They also have a browser open in case they need to do any research.
- Driver: is typing and their role is to translate the high level task into code ("Set the scroll view delegate, implement the didScroll method").
- After you finish each checkpoint, switch the Navigator and Driver roles. The person on the right will be the first Navigator.
- User can view and scroll through a list of Yelp restaurants.
- From the
+
objects library button, search and add the tableView to your storyboard:- ==NOTE:== You can reference steps 1-4 of the Basic Table View Guide
- Connect your tableView outlet to your
RestaurantsViewController
file - Open the your
RestaurantsViewController
file by holding the alt/option key and click on the file - Hold the control key, then drag and drop the tableView to your View controller:
- Add a
tableViewCell
from+
objects library +- Inside the cell, add a
label
and anImage View
as well:
- Inside the cell, add a
- Create
RestaurantCell
file under theCells
folder- Configure cell's Class and Identifier on the 'Identity Inspector' + 'Attribute Inspector' to
RestaurantCell
:
- Configure cell's Class and Identifier on the 'Identity Inspector' + 'Attribute Inspector' to
Reference: Creating a Custom Cell Guide
- Create cell's label + image outlets on the
RestaurantCell
file:
On the lab starter, take a look at the exampleAPI.json
file to get yourself familiarized with the JSON format of the response data from the API.
Note: For the purposes of this lab, we won't go into a lot of detail about the network request code because it's mainly a lot of repetitive configuration that--for the purposes of this course--won't ever change. Whats important for you to know are the next steps:
:::warning :bulb: However, you can learn more about networks and APIs in our course guides and in this in-depth slide deck about API requests in Swift. :::
1. Create a Yelp Account and generate an API Key
- Once generated, go to your
Network
folder and copy/paste your API key on theAPI.swift
- Next, lets add the logic to our API request:
- Under the "TODO" section, type this:
Here is the flow of the code:
- Traverse the data in JSON Format and convert it to a dictionary.
- From the dictionary, the "businesses" value is an array of businesses, so we convert it to an array of dictionaries that represent the data of each restaurant
- Return the array of dictionaries representing the restaurants
- "What happens in closures, stay in closures." In order to get the data inside the closure, we use the @escaping method using the variable
completion
to return it.
- "What happens in closures, stay in closures." In order to get the data inside the closure, we use the @escaping method using the variable
:::warning Closures are basically functions within a function. Make sure to review the Swift fundamentals to understand them! :bulb: Tip: If you are having trouble understanding it at first glance, don't worry! No one really understands code at first glance. It takes a while to fully get the flow of how code works. But be sure to always ask questions! :::
- Initialize your
restaurantsArray
on yourRestaurantsViewController
file. It should look something like this:
var restaurantsArray: [[String:Any?]] = []
The restaurantsArray
is our placeholder for storing all the data we get from the API request
- Create a function
getAPIData()
that retrieves the data from the API and stores the data in ourrestaurantsArray
variable:
- Add the tableView.delegate + tableView.dataSource on
viewDidLoad()
:
// ––––– TODO: Add tableView datasource + delegate
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
- Add the protocols for
UITableViewDelegate
andUITableViewDataSource
to yourRestaurantsViewController
class:
class RestaurantsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
...
}
- An error should pop up. In that error, click on "fix" to automatically add the protocol stubs and place them at the bottom of all your code:
- ==Note:== The
numberOfRowsInSection
method simply tells the table view how many rows, or cells in this case, to create. How many cells do we want? Well, as many as we have restaurants. We can get that number by calling thecount
method on ourrestaurantsArray
array. So, instead of returning a hard coded number like5
we will want toreturn restaurantsArray.count
. This is where you can get into trouble ifrestaurantsArray
containsnil
which is why we initializedrestaurantsArray
as an empty array because although empty, it is notnil
.
- Configure
numberOfCellsInSection
protocol stub:
-
Configure our custom RestaurantViewCell:
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantCell", for: indexPath) as! RestaurantCell
-
Configure
cellForRowAt
protocol stub:
-
References: Setting up the Image View in your Custom Cell:
-
Each cell will need a single
UIImageView
. Make sure to create an outlet from the image view to your RestaurantCell class and not your RestaurantsViewController class; after all, we created a the custom cell class to control the properties of our reusable cell. DO NOT name this outletimageView
to avoid colliding with the defaultimageView
property of theUITableViewCell
base class. -
NOTE: The
tableView(_:cellForRowAt:)
method is called each time a cell is made or referenced. Each cell will have a uniqueindexPath.row
, starting at0
for the first cell,1
for the second and so on. This makes theindexPath.row
very useful to pull out objects from an array at particular index points and then use the information from a particular object to populate the views of our cell. -
In the
tableView(_:cellForRowAt:)
method, pull out a singlerestaurant
from ourrestaurantsArray
arraylet restaurant = restaurantsArray[indexPath.row]
-
-
Getting the image from the restaurant dictionary:
- It's possible that we may get a
nil
value for an element in therestaurantArray
, i.e. maybe no images exist for a given restaurant. We can check to make sure it is notnil
before unwrapping. We can check using a shorthand swift syntax called if let restaurant
is a dictionary containing information about the restaurant. We can access therestaurantArray
array of arestaurant
using a key and subscript syntax.
- It's possible that we may get a
-
Implementation to getting the image:
-
💡 This is the url location of the image. We'll use our AlamofireImge helper method to fetch that image once we get the url.
- Get the image url string from the restaurant dictionary
- Get the convert url string –> url
- set image using the image url with AlamofireImage
// 1. if let imageUrlString = restaurant["image_url"] as? String { // 2. let imageUrl = URL(string: imageUrlString) // 3. cell.restaurantImage.af.setImage(withURL: imageUrl!) }
-
-
Set the image view
-
We'll be bringing in a 3rd party library to help us display our restaurant image. To do that, we'll use a library manager called CocoaPods. If you haven't already, install CocoaPods on your computer now.
-
Navigate to your project using the Terminal and create a podfile by running,
pod init
. -
Add
pod 'AlamofireImage'
to yourpodfile
, this will bring in the AlamofireImage library to your project. -
In the Terminal run,
pod install
. When it's finished installing your pods, you'll need to close yourxcodeproj
and open the newly createdxcworkspace
file. -
import the AlamofireImage framework to your file. Do this at the top of the file under
import UIKit
. This will allow the file access to the AlamofireImage framework.import AlamofireImage
-
call the AlamofireImage method,
af_setImage(withURL:)
on your image view, passing in the url where it will retrieve the image.cell.restaurantImage.af.setImage(withURL: imageUrl!)
-
-
Our table view will likely be created before we get our data back from the network request. Anytime we have fresh or updated data for our table view to display, we need to call:
-
Do this inside the getAPIData() function, right after we load the data we got back into our
restaurantsArray
property.// ––––– TODO: Get data from API helper and retrieve restaurants func getAPIData() { API.getRestaurants() { (restaurants) in guard let restaurants = restaurants else { return } self.restaurantsArray = restaurants self.tableView.reloadData() // reload data! } }
-
Call our
getAPIData()
fromviewDidLoad()
override func viewDidLoad() { ... getAPIData() }
- Make your Yelpy app look like the one shown in the GIF introduction of the lab!
- You can download the star ratings images from Yelp here
-
If your app crashes with the exception:
Unknown class RestaurantsViewController in Interface Builder file
, try following the steps in this stackoverflow answer. -
Compile Error: "No such module AlamofireImage"
- Try cleaning and building your project. Command + Shift + K and Command + B