A student that completes this project shows that they can:
- understand and explain common use cases for enums
- define custom enum types
- use documentation to understand how to use a REST API
- understand and explain the purpose of JSON
- use URLSession to make a GET request to a URL
- implement a URLSessionDataTask completion closure
- use JSONDecoder() to convert JSON data returned by an API into model objects
iTunes Search allows you to search for apps, music and movies using the iTunes Search API. You will practice using enums, GET requests using URLSessionDataTask, using API documentation and parsing JSON.
Please look at the screen recording below to know what the finished project should look like:
Please fork and clone this repository. This repository does not have a starter project, so create one inside of the cloned repository folder.
This application uses the iTunes Search API. Please familiarize yourself with the API's documentation here. Read the overview, and look at the "Searching" section. There are some search examples that are worth looking at as well.
We're going to search for either an app, music, or movie. Use this URL to see some example JSON. NOTE: the URL will download a file to your desktop which contains the JSON. You will need to open the file in a text editor to see the JSON. It's recommended that you then copy and paste the JSON in a JSON formatter.
- Create a new Swift file "SearchResult.swift", and create a struct called
SearchResult. Adopt theCodableprotocol. - Add the following properties:
- a
titlestring - an
creatorstring.
- a
Note: The JSON returned from the iTunes Search API refers to the developers of an application as the artistName, and the title of the search result as the trackName. The same goes for the trackName for the title of the search result. No matter whether it's music, movies, apps, etc. the JSON uses these same key-value pairs to keep the JSON consistent.
You may have noticed that the title and creator properties in this struct don't match any keys in the JSON. As a recap, a class or struct that adopts Codable will by default use the names of its properties as the keys it should look for in the JSON that it is trying to decode. However there are instances where the keys in the JSON perhaps aren't what you'd like to call your properties throughout your app. We can implement another part of Codable that lets us override this behavior called CodingKeys. CodingKeys allow us to and map the keys from the JSON to the properties we want their values to be stored in. Still in the SearchResult struct, add the following:
enum CodingKeys: String, CodingKey {
}- This enum has
Stringraw values, and it conforms to theCodingKeyprotocol. Now, add acasefortitlein the enum. Give it a raw value of"trackName". When the decoder is going through the JSON, it will now look for a key calledtrackName, but instead of trying to put that value in a property calledtrackName(which in this struct doesn't exist, thus making the decoding fail), it knows to put the value in a property calledtitlebecause that is the name of the raw value's case. Make a case forcreator. Its raw value should be"artistName".
Take a minute to look at the example json at the URL at the start of part 1. The JSON objects that represent each SearchResult are nested in the JSON. If we were to try to use a JSONDecoder() and try to decode it into [SearchResult] it wouldn't work. We need to create an object that represents the JSON at the highest level (the object with resultCount and results keys).
- Under the
SearchResultstruct, add aSearchResultsstruct. Create aresults: [SearchResult]constant. This will allow us to decode the JSON data into this object, then access the actual search results through itsresultsproperty.
- Create a new Swift file called "ResultType.swift".
- Create an enum called
ResultType. Set its raw values to beString. This will represent the kind of item you want to search for in the API. - Add the following cases:
software- (for apps)musicTrackmovie
- Create a new Swift file called "SearchResultController.swift", and make a class called
SearchResultController. - Add a
baseURLconstant. This should be the base URL for the iTunes Search API. - Add a
searchResults: [SearchResult] = []variable. This will be the data source for the table view. - Create a
performSearchfunction with asearchTerm: String, aresultType: ResultTypeparameter, and acompletionclosure. The completion closure should take anError?argument and should returnVoid. As a first measure of help for closure syntax, look at the "As a parameter to another function" section of this page. You're obviously free to ask a PM for help as well. - Create your full request url by taking the
baseURL, and adding the necessary query parameters (in the form ofURLQueryItems.) to it usingURLComponents. - This function should use
URLSession'sdataTask(with: URL, completion: ...)method to create a data task. Remember to call.resume(). - In the completion closure of the data task:
- Give names to the return types.
- Check for errors. If there is an error, call completion with the error.
- Unwrap the data. If there is no data, call completion, and return
NSError()in it. - If you do get data back, use a do-try-catch block and
JSONDecoderto decodeSearchResultsfrom the data returned from the data task. Create a constant for this decodedSearchResultsobject. - Set the value of the
searchResultsvariable in this model controller to theSearchResults'resultsarray. - Still in the
dostatement, call completion withnil. - In the
catchstatement, call completion witherror.
- In the Main.storyboard delete the pre-added
UIViewControllerscene. - Add a
UITableViewControllerscene. Embed it in a navigation controller, and set it as the initial view controller. - Set the navigation item's title to "iTunes Search"
- Change the cell's style to "Subtitle"
- Add a
UIViewas the table view's header view. HINT: To do this, you drag theUIViewright above the table view, and it should expand to the width of the table view, then you can drop it there. Select theUIViewand expand it to about 100 points. - Add a
UISegmentedControlto the header view. This segmented control will let the user control which type of item they search for in the API. Select the segmented control, and in the Attributes Inspector:- Change the number of segments to three.
- Set the first segment's title to "Apps", the second's to "Music" and the third's to "Movies".
- Add a
UISearchBarto the header view under the segmented control. - Constrain the segmented control and the search bar.
- Create a Cocoa Touch Subclass of
UITableViewController. Call itSearchResultsTableViewController. Set the table view controller scene's class to this new subclass. - Create an outlet from the segmented control and the search bar.
In the SearchResultsTableViewController
- Create a constant called
searchResultsControllerwhose value is a new instance ofSearchResultController. - Using the instance of
SearchResultController, fill out thenumberOfRowsInSectionandcellForRowAtmethods. Each cell should display thetitleandartistof aSearchResultObject.
When using a UISearchBar, you use a method in the UISearchBarDelegate to trigger searches when the user taps the search button on their keyboard. This method is called searchBarSearchButtonClicked.
- In order to use the
searchBarSearchButtonClickedmethod, adopt theUISearchBarDelegateprotocol in your table view controller. - In the
viewDidLoad, set the search bar'sdelegateproperty to the table view controller. - Call the
searchBarSearchButtonClickedmethod. This method should take the search term that the user enters in, and trigger a search for the specific result type they specify using the segmented control. Look below for step-by-step instructions on how to do this if needed.
Step-by-step implementation of searchBarSearchButtonClicked
- Unwrap the search bar's text
- Create a variable
resultType: ResultType!. This will hold the result type selected from the segmented control. - Using a conditional statement like a
switchorif-else, check the segmented control'sselectedSegmentIndexproperty. TheselectedSegmentIndexis an integer value that represents which segment is currently selected. Since this segmented control has three segments, its possible indexes will be 0, 1, or 2. Set theresultType's value to the correct case for each possibleselectedSegmentIndexvalue. (for example, 0 would be.softwaresince the selected segment would be apps.) - Call the
performSearchmethod of thesearchResultsController. Pass in the search term, and theresultType. In the completion closure of this method, check for errors. If there is no error, reload the table view on the correct queue.
- Perform a search every time the user selects a new segment on the segmented control.
- Allow for limiting searches by country and/or by a number of desired search results.
- Refer back to the JSON and make the
searchResultobject have additional properties. Create a custom cell and/or a detail view controller to display these extra properties to the user.
