Students will build an app to get the representives (law makers) in a user-requested state to practice asyncronous network requests, working with JSON data, closures, and intermediate table views.
Students who complete this project independently are able to:
- use NSURLSession to make aysncronous network calls
- parse JSON data and generate model object from the data
- use closures to execute code when an asyncronous task is complete
- build custom table views
Create a Representative model class that will hold the information of a representative to display to the user.
- Create a
Representative.swiftclass file and define a newRepresentativeclass. - Go to a sample endpoint of the Who is my Representative API and see what JSON (information) you will get back.
- Using this information, add properties on
Representative.let name: Stringlet party: Stringlet state: Stringlet district: Stringlet phone: Stringlet office: Stringlet link: String
- Create a failable initializer method with a parameter of a JSON dictionary (
[String: AnyObject]. This is the method you will use to initialize yourRepresentativeobject from the JSON dictionary. Remember to use a sample endpoint to inspect the JSON you will get back and the keys you will use to get each piece of data.
Create a NetworkController class. This will have methods to build the different URLs you might want and it should have a method to return NSData from a URL.
The NetworkController will be responsible for building URLs and executing HTTP requests. Build the NetworkController to support different HTTP methods (GET, PUT, POST, PATCH, DELETE), and keep things generic such that you could use the same NetworkController in future projects if desired.
It is good practice to write reusable code. Even when you do not plan to reuse the class in future projects, it will help you keep the roles of your types properly separated. In this specific case, it is good practice for the NetworkController to not know anything about the project's model or controller types.
- Write a
StringtypedenumcalledHTTPMethod. You will use this enum to classify our HTTP requests as GET, PUT, POST, PATCH, or DELETE requests. Add cases for each.- example:
case Get = "GET"
- example:
- Write a function signature
performRequestForURLthat will take anNSURL, anHTTPMethod, an optional[String: String]dictionary of URL parameters, an optionalNSDatarequest body, and an optional completion closure. The completion closure should include aNSData?data parameter and anNSError?error parameter, and the should returnVoid.- note: At this point, it is OK if you do not understand why you are including each parameter. Spend some time contemplating each parameter and why you would include it in this function. For example: An HTTP request is made up of a URL, and an HTTP Method. Certain requests need URL parameters. Certain POST or PUT requests can carry a body. The completion closure is included so you know when the request is complete.
- Add the following
urlFromURLParametersfunction to yourNetworkControllerclass. This function takes a base URL, URL parameters, and returns a completed URL with the parameters in place.- example: To perform a Google Search, you use the URL
https://google.com/search?q=test. 'q' and 'test' are URL parameters, with 'q' being the name, and 'test' beging the value. This function will take the base URLhttps://google.com/searchand a[String: String]dictionary["q":"test"], and return the URLhttps://google.com/search?q=test
- example: To perform a Google Search, you use the URL
static func urlFromURLParameters(url: NSURL, urlParameters: [String: String]?) -> NSURL {
let components = NSURLComponents(URL: url, resolvingAgainstBaseURL: true)
components?.queryItems = urlParameters?.flatMap({NSURLQueryItem(name: $0.0, value: $0.1)})
if let url = components?.URL {
return url
} else {
fatalError("URL optional is nil")
}
}- Implement the
performRequestforURLfunction.- Use the
urlFromURLParametersto get a requestURL. - Creating a new
NSMutableURLRequest, set the HTTP method, set the body. - Generate and start the data task.
- Calling the completion when the data task completes.
- Use the
This method will make the network call and call the completion closer with the NSData? result. If successful, NSData? will contain the response, if unsuccessful, NSData? will be nil. The class or function that calls this function will need to handle nil data.
- Use a Playground to test your network controller method with a sample endpoint from the Who is My Representative API to see if you are getting data returned.
As of iOS 9, Apple is boosting security and requiring developers to use the secure HTTPS protocol and requires the server to use the proper TLS Certificate version. The Post API does support HTTPS but does not use the correct TLS Certificate version. So for this app, you will need to turn off the App Transport Security feature.
- Open your
Info.plistfile and add a key-value pair to your Info.plist. This key-value pair should be:App Transport Security Settings : [Allow Arbitrary Loads : YES].
Create a RepresentativeController class. This class will use the NetworkController to fetch data, and will serialize the results into Representative objects. This class will be used by the view controllers to fetch Representative objects through completion closures.
- The
RepresentativeControllershould have some static constant that represents thebaseURLof the API. - Add a method
searchRepsByStatethat allows the developer to pass in the search parameter and, through a completion closure, provide an array ofRepresentativeobjects.- This method should set URL parameters for the state and the output types.
- This method should call the NetworkController's
performRequestforURLmethod to get the NSData at the URL created in the previous bullet point. - In the closure of the
performRequestforURL, use a guard to check for nil NSData, and to unwrap the array of[String: AnyObject]dictionaries that hold Representative data. You will need to use thetry?keyword to useNSJSONSerializationto serialize theNSData. - If the guard fails, print an error message to the console and run the completion with an empty array.
- If the NSData can be serialized, create a
Representativeobjects and call the completion closure with the populated array. (Hint: Use a for loop orflatMapto iterate through the dictionaries and initialize a new array ofRepresentativeobjects.)
Note: There are many different patterns and techniques to serialize JSON data into Model objects. Feel free to experiment with different techniques to get at the [String: AnyObject] dictionaries within the NSData returned from the NSURLSessionDataTask.
At this point you should be able to pull data for a specific state and serialize a list of Representatives. Test this functionality with a Playground or in your App Delegate by trying to print the results for a state to the console.
You will implement a 'Master-Detail' view hierarchy for this application.
Your Master View is a list of states. Selecting any state will segue to a Detail View displaying a list of representatives for the selected state.
Recognize that even though the list of representatives may be called a List view, it is also considered a Detail view in this project, because it is the Detail view of the selected state.
Build a view that lists all states. Use the included StateController.states() variable to build the datasource for the UITableViewController. This view will be used to segue to a list of Representatives for the selected state.
- Add a
UITableViewControlleras your root view controller in Main.storyboard and embed it into aUINavigationController - Create an
StateListTableViewControllerfile as a subclass ofUITableViewControllerand set the class of your root view controller scene - Implement the UITableViewDataSource functions using the included
StateControllerstates array - Set up your cells to display the name of each state
Build a view that lists all of the Representatives for a selected state. Use a UITableViewController and a custom UITableViewCell that displays the properties of a Representative (name, party, state, district, phone, and url).
The State List View Controller will pass a State string to this scene. We will use that value to perform the network request. When the network request is completed, you will reload the UITableView to display the results.
- Add an additional
UITableViewControllerscene to the Storyboard. Create a class fileStateDetailTableViewController.swiftand assign the class to the storyboard scene. - Create a prototype cell that uses a Stack View to display the name, party, district, website, and phone number of a
Representative. - Create a custom
UITableViewCellclass with anupdateWithRepresentativefunction that sets the labels to theRepresentativedata, and assign the prototype cell to the class. - Add a property of type
[Representative]that will be used to populate the Table View. Set it to an empty array to satisfy the requirement that all properties have values upon initialization. - Add an optional
stateproperty of typeString. This will be set by theStateListViewControllerin theprepareForSeguefunction. - Implement the UITableViewDataSource functions to return your custom prototype, use the
updateWithRepresentativefunction to display the correctRepresentativedata on each cell. - Update your
viewDidLoadfunction to call theRepresentativeController.searchRepsByStatefunction using the unwrapped state property. In the completion closure, setself.stateRepresentativesto the returned representatives and reload the UITableView on the main thread.
Note: It is good practice to let the user know that a network request is processing. This is most commonly done using the Network Activity Indicator in the status bar. Look up the documentation for networkActivityIndicatorVisible property on UIApplication to turn on the indicator when the view loads, and turn it off when the network call is complete.
Set up a segue from the State List View to the State Detail View that assigns the state that the State Detail View should load Representatives for.
- Add a segue from the prototype cell on the
StateListTableViewControllerscene to theStateDetailTableViewControllerand assign an identifier. - Implement the
prepareForSeguefunction on theStateListTableViewControllerclass to capture the state and assign it to thedestinationViewController'sstateproperty.
- Notice how after tapping the search button, the app hangs and doesn't do anything while the network call is being performed. Give visual feedback to the user that the search is being conducted.
- Implement another way for users to find their Congressman/Congresswoman.
- If no Represenatives were "found", notify the user that a search failed.
- Make the phone, office, and website labels links that would call, open a map view, and open a web view.
Please refer to CONTRIBUTING.md.
© DevMountain LLC, 2015. Unauthorized use and/or duplication of this material without express and written permission from DevMountain, LLC is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to DevMountain with appropriate and specific direction to the original content.