Creating RestaurantDataManager

During WWDC 2017, Apple introduced a new way to parse JSON files, which is much simpler than the previous way of doing it. You implemented the old way of parsing JSON files earlier in RestaurantAPIManager. Now, you will see the new way of doing it, and this will be useful when you need to convert older projects. This also shows you that it is relatively easy to change the data manager class without making a lot of changes to the rest of your project.

To learn more about the new way of parsing JSON files, watch the video available here: https://developer.apple.com/videos/play/wwdc2017/212/.

You will start by modifying RestaurantItem so it conforms to the Decodable protocol:

  1. In the Project navigator, click RestaurantItem.swift inside the Model folder in the Map folder. Modify the class declaration for RestaurantItem as shown:
class RestaurantItem: NSObject, MKAnnotation, Decodable {
  1. Remove the init() method and add the following code:
enum CodingKeys: String, CodingKey {
case name
case cuisines
case lat
case long
case address
case postalCode = "postal_code"
case state
case imageURL = "image_url"
}

The CodingKeys enumeration matches the RestaurantItem properties to the keys in the JSON file. If the key name does not match the property name, you can map the key name to the property name using the = sign, as shown in the preceding code block for postalCode and imageURL.

Next, you will write a new data manager named RestaurantDataManager that uses the new way of getting data from JSON files. It then puts the data into an array of RestaurantItems instances. Perform the following steps:

  1. Right-click on the Restaurants folder and create a new group named Model. Then, right-click on the Model folder and choose New File.
  2. iOS should already be selected. Choose Swift File and then click Next.
  3. Name this file RestaurantDataManager. Click Create.
  4. Add the following under the import statement to declare the RestaurantDataManager class:
class RestaurantDataManager {

}
  1. Add the following property between the curly braces to hold an array of RestaurantItem instances:
private var items:[RestaurantItem] = []

Here, items is private, which means it is only accessible from within this class.

  1. Add the following method to the class:
func fetch(by location:String, with filter:String = "All", 
completionHandler:(_ items:[RestaurantItem]) -> Void) {
if let file = Bundle.main.url(forResource: location,
withExtension: "json") {
do {
let data = try Data(contentsOf: file)
let restaurants = try JSONDecoder().decode([RestaurantItem].self,
from: data)
if filter != "All" {
items = restaurants.filter({ ($0.cuisines.contains(filter))})
}
else { items = restaurants }
}
catch {
print("there was an error (error)")
}
}
completionHandler(items)
}

Let's break this down:

  • func fetch(by location:String, with filter:String = "All", completionHandler:(_ items:[RestaurantItem]) -> Void)
    This function takes two parameters: location, a string containing the restaurant location, and filter, a string containing cuisines. If you do not provide a value for filter, it will default to "All". A completion handler is used to assign the result of this method to the items property when it has finished execution.
  • if let file = Bundle.main.url(forResource: location, withExtension: "json")
    This gets the path of the JSON file in the app bundle and assigns it to file.
  • do {
    let data = try Data(contentsOf: file)
    let restaurants = try JSONDecoder().decode([RestaurantItem].self,
    from: data)
    if filter != "All" {
    items = restaurants.filter({ ($0.cuisines.contains(filter))})
    }
    else { items = restaurants }

The first line attempts to assign the contents of file to data. The next line attempts to parse data and decode it as an array of RestaurantItem instances, which is assigned to restaurants. In the next line, if filter is not All, the filter method is applied to the restaurants array using the { ($0.cuisines.contains(filter))} closure. This results in an array of RestaurantItem instances where the cuisines property contains the filter value, and this array is assigned to items. Otherwise, the entire restaurants array is assigned to items.

  • catch {
    print("there was an error (error)")
    }
    This prints an error message to the Debug area if the
    do block fails.
  • completionHandler(items)
    The result of this method is assigned to the items property. 
    Note that when you type this method in Xcode, the autocomplete feature gives you two possible choices; one that includes the with: parameter (that takes a filter string) and one that doesn't (filter is set to All).
  1. Add the following method to the class just after the previous method:
func numberOfItems() -> Int {
return items.count
}

This method returns the number of items in the items array:

  1. Add the following method to the class just after the previous method:
func restaurantItem(at index:IndexPath) -> RestaurantItem {
return items[index.item]
}

This method returns the RestaurantItem from the items array located at the index given:

Now you have RestaurantDataManager, you can use it not only to provide restaurant data to the Restaurant List screen but also the Map screen. Let's see how to do this in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.237.79