The basic idea of the ViewController is to show the name of the restaurant and its rating stars. The name of the restaurants comes from my API in JSON and the rating is from Google places API.
I first get the names from the server:
func getData (){
let urlStr = "http://myserver.com/api/"
let url = URL(string: urlStr)
Alamofire.request(url!, method: .get ,encoding: URLEncoding.default).responseJSON { response in
if let value : AnyObject = response.result.value as AnyObject {
let shops = JSON(value)
for (key,subJson):(String, JSON) in shops["families"] {
if subJson["disable"] == "0"{
let shopName = subJson["name"].stringValue
self.performSereach(shopName: shopName)//do search
// take the name and
var info = Shops(shopname: shopName,
Logo: logoString, family_id: familiy_id , design : designImage)
// save the data to show in TableVIewCell
Now I perform a search in Google places with the link
https://maps.googleapis.com/maps/api/place/textsearch/json?query=\(shopName)+in+SanFrancisco&key=MyKey
The function to do the search :
func performSereach(shopName : String ) {
let formattedString = shopName.replacingOccurrences(of: " ", with: "+")
let urlStr = "https://maps.googleapis.com/maps/api/place/textsearch/json?query=\(formattedString)+in+ SanFrancisco&key=MyKey"
Alamofire.request(urlStr).responseJSON { response in
if let value : AnyObject = response.result.value as AnyObject {
let data = JSON(value)
for (key,subJson):(String, JSON) in data["results"] {
print(subJson["rating"])
print(subJson["name"])
But when I do this I feel like I'm doing it wrong because:
1- There are so many Starbucks in my area so the response will have more than one rating ( in this case I would like to take the first one) And the print statements prints all the Starbucks and its rating
2- how can I connect the shopName with its rating star at the end of the search in Shops ?
Shops is:
import Foundation
class Shops {
var _familiy_id: String?
var _logo : String?
var _design: String?
var _rate : Int
var _shopname : String?
var rate : Int{
return _rate
}
var familiy_id : String{
return _familiy_id!
}
var shopname : String{
return _shopname!
}
var Logo : String{
return _logo!
}
var design : String {
return _design!
}
init(shopname : String , Logo : String , family_id : String, design : String , rate : Int) {
self._shopname = shopname
self._logo = Logo
self._familiy_id = family_id
self._design = design
self._rate = rate
}
}
Related
Gallery is the array of ProviderGallery in this below model. I am trying to fetch specific variable of ProviderGallery to array without using forloop. The below code i used for loop to fetch the desired element.
Model :
struct ProviderProfileData : Codable{
let message : String?
let gallery : [ProviderGallery]?
}
struct ProviderGallery : Codable {
let id : Int?
let file_name : String?
let thumb : String?
let mime_type : String?
let duration : String?
let size : String?
}
JSONDecoder :
do {
let decoder = JSONDecoder()
let providerProfileDetails = try decoder.decode(ProviderProfileData.self, from: data)
print("data \(providerProfileDetails)")
// Here i am getting desired value into array using forloop
if let gallery = providerProfileDetails.data.gallery {
var thumbArray = [String]()
for i in 0..<gallery.count{
thumbArray.append(gallery[i].thumb ?? "")
}
print("thumbs \(thumbArray)")
}
}catch let error {
print("Error \(error.localizedDescription)")
}
Using compactmap to remove all nil value or using map if you want to replace nil value:
let thumArray = gallery.compactMap({ return $0.thumb })
let thumArray = gallery.map({ return $0.thumb ?? ""})
Im creating an app that needs to be able to determine what locations are in a given area and then retrieve details about those locations, which then will be displayed. To do this I've been using Google Places API, but I am having trouble retrieving place details for locations using their place_id.
At the moment I am able to successfully perform a Google Places 'Nearby Search' request which returns many locations and some basic data about them (including a place_id). What I'm having trouble with is using the place-id's I receive from the first request to make a 'Place Details' request to get all the information Google has about the respective location.
vv My code below details my current attempt vv
function where I call the 2 requests:
func requestAndCombineGData(location: CLLocation, radius: Int) {
self.mapView.clear()
// Calls 'Nearby Search' request
googleClient.getGooglePlacesData(location: location, withinMeters: radius) { (response) in
print("Made Nearby Search request. Passed response here:", response)
// loops through each result from the 'Nearby Request' to get the 'place_id' and make 'Place Details'
for location in response.results {
// Calls 'Place Details' request
self.googleClient.getGooglePlacesDetailsData(place_id: location.place_id) { (detailsResponse) in
print("Made Place Details request. Passed response here:", detailsResponse)
// function to drop markers using data received above here
// self.putPlaces(places: response.results)
}
}
}
}
GoogleClient.swift file that contains the code for handling the requests above:
import SwiftUI
import Foundation
import CoreLocation
//Protocol
protocol GoogleClientRequest {
var googlePlacesKey : String { get set }
func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: #escaping (GooglePlacesResponse) -> ())
func getGooglePlacesDetailsData(place_id: String, using completionHandler: #escaping (GooglePlacesDetailsResponse) -> ())
}
// GoogleClient class that conforms to the ZGoogleClient Request protocol
class GoogleClient: GoogleClientRequest {
let session = URLSession(configuration: .default)
var googlePlacesKey: String = "MY_KEY_GOES_HERE"
let categoriesArray = [
"park",
"restaurant",
"zoo"
]
func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: #escaping (GooglePlacesResponse) -> ()) {
for category in categoriesArray {
let url = googlePlacesNearbyDataURL(forKey: googlePlacesKey, location: location, radius: radius, type: category)
let task = session.dataTask(with: url) { (responseData, _, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = responseData, let response = try? JSONDecoder().decode(GooglePlacesResponse.self, from: data) else {
print("Could not decode JSON response")
completionHandler(GooglePlacesResponse(results:[]))
return
}
if response.results.isEmpty {
print("GC - response returned empty", response)
} else {
print("GC - response contained content", response)
completionHandler(response)
}
}
task.resume()
}
}
func getGooglePlacesDetailsData(place_id: String, using completionHandler: #escaping (GooglePlacesDetailsResponse) -> ()) {
let url = googlePlacesDetailsURL(forKey: googlePlacesKey, place_ID: place_id)
let task = session.dataTask(with: url) { (responseData, _, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = responseData, let detailsResponse = try? JSONDecoder().decode(GooglePlacesDetailsResponse.self, from: data) else {
print("Could not decode JSON response. responseData was: ", responseData)
completionHandler(GooglePlacesDetailsResponse(results:[]))
return
}
// print("response result: ", detailsResponse.results)
if detailsResponse.results.isEmpty {
print("getGPDetails - response returned empty", detailsResponse)
} else {
print("getGPDetails - response contained content", detailsResponse)
completionHandler(detailsResponse)
}
}
task.resume()
}
func googlePlacesNearbyDataURL(forKey apiKey: String, location: CLLocation, radius: Int, type: String) -> URL {
print("passed location before url creation ", location)
print("passed radius before url creation ", radius)
let baseURL = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
let locationString = "location=" + String(location.coordinate.latitude) + "," + String(location.coordinate.longitude)
let radiusString = "radius=" + String(radius)
let typeString = "type=" + String(type)
// let rankby = "rankby=distance"
// let keywrd = "keyword=" + keyword
let key = "key=" + apiKey
print("Request URL:", URL(string: baseURL + locationString + "&" + radiusString + "&" + type + "&" + key)!)
return URL(string: baseURL + locationString + "&" + radiusString + "&" + typeString + "&" + key)!
}
func googlePlacesDetailsURL(forKey apiKey: String, place_ID: String) -> URL {
print("passed place_ID before url creation ", place_ID)
let baseURL = "https://maps.googleapis.com/maps/api/place/details/json?"
let place_idString = "place_id=" + place_ID
let fields = "fields=rating"
let key = "key=" + apiKey
print("Details request URL:", URL(string: baseURL + place_idString + "&" + fields + "&" + key)!)
return URL(string: baseURL + place_idString + "&" + fields + "&" + key)!
}
}
ResponseModels.swift file that holds the structs to handle the 2 different request responses:
import SwiftUI
import Foundation
struct GooglePlacesResponse : Codable {
let results : [Place]
enum CodingKeys : String, CodingKey {
case results = "results"
}
}
// Place struct
struct Place : Codable {
let geometry : Location
let name : String
let place_id: String
let openingHours : OpenNow?
let photos : [PhotoInfo]?
let types : [String]
let address : String
enum CodingKeys : String, CodingKey {
case geometry = "geometry"
case name = "name"
case place_id = "place_id"
case openingHours = "opening_hours"
case photos = "photos"
case types = "types"
case address = "vicinity"
}
// Location struct
struct Location : Codable {
let location : LatLong
enum CodingKeys : String, CodingKey {
case location = "location"
}
// LatLong struct
struct LatLong : Codable {
let latitude : Double
let longitude : Double
enum CodingKeys : String, CodingKey {
case latitude = "lat"
case longitude = "lng"
}
}
}
// OpenNow struct
struct OpenNow : Codable {
let isOpen : Bool
enum CodingKeys : String, CodingKey {
case isOpen = "open_now"
}
}
// PhotoInfo struct
struct PhotoInfo : Codable {
let height : Int
let width : Int
let photoReference : String
enum CodingKeys : String, CodingKey {
case height = "height"
case width = "width"
case photoReference = "photo_reference"
}
}
}
struct GooglePlacesDetailsResponse : Codable {
let results : [PlaceDetails]
enum CodingKeysDetails : String, CodingKey {
case results = "results"
}
}
// PlaceDetails struct
// I have fields commented out because I wanted to just get a location's rating for testing before implementing the rest
struct PlaceDetails : Codable {
// let place_id: String
// let geometry : Location
// let name : String
let rating : CGFloat?
// let price_level : Int
// let types : [String]
// let openingHours : OpenNow?
// let formatted_address : String
// let formatted_phone_number : String
// let website : String
// let reviews : String
// let photos : [PhotoInfo]?
enum CodingKeysDetails : String, CodingKey {
// case place_id = "place_id"
// case geometry = "geometry"
// case name = "name"
case rating = "rating"
// case price_level = "price_level"
// case types = "types"
// case openingHours = "opening_hours"
// case formatted_address = "formatted_address"
// case formatted_phone_number = "formatted_phone_number"
// case website = "website"
// case reviews = "reviews"
// case photos = "photos"
}
}
Google Places API documentation I've been referencing:
https://developers.google.com/places/web-service/search
-
Like I said above, right now my first request ('Nearby Search') is successfully returning data, but when I try to implement the second request ('Place Details') using a 'place_id'. My console keeps returning "Could not decode JSON response. Response: Optional(106 bytes)" which comes from a comment in my 'GoogleClient.swift' file.
My questions are:
What is wrong with how I'm making the 'Place Details' request that is causing it to return that the response cant be decoded?
AND
Is there a better way to make a Nearby Search request then use the 'place_id' from the returned locations to make a Place Details request in swift?
It looks like the issue you're having is with your model:
struct GooglePlacesDetailsResponse : Codable {
let results : [PlaceDetails]
enum CodingKeysDetails : String, CodingKey {
case results = "results"
}
}
According to the documentation there is no key on the place details response for "results" the correct key is "result" non-plural. Also you define the model as an array of the struct PlaceDetails, which is incorrect. When you return place details using a place id it will return only one object, which is the place details for that specific id as the place id is a unique value. I expect if you correct your response model your issues should resolve.
I'm new to iOS development and I understand that allowing optional values when an object is initialized is not a 'good citizen' technique. That being said, I've read that it is good practice to always have values set, like this:
class Item{
var name: String
var color: String
init(name: String, color: String) {
self.name = name
self.color = color
}
}
This looks nice and tidy but how can I do something like that working with Firebase? Look what I've got so far:
private func loadPosts(){
databaseHandle = ref.child("users/\(self.user.uid)/posts").observe(.value, with:{(snapshot) in
var newPosts = [Post]()
for itemSnapShot in snapshot.children {
let post = Post(snapshot: itemSnapShot as! FIRDataSnapshot)
newPosts.append(post!)
}
self.posts = newPosts
self.tableView.reloadData()
})
}
This guy is placed in my PostsViewController where I have my table view. This is my model:
class Post {
var ref: FIRDatabaseReference?
var title: String?
var answer: String?
var contentUrl: String?
var photoUrl: String?
var createdAt: String?
var feeling: String?
var kind: String?
var text: String?
var uid: String?
var measurements: Dictionary<String, String>?
//MARK: Initialization
init?(snapshot: FIRDataSnapshot){
ref = snapshot.ref
let data = snapshot.value as! Dictionary<String, Any>
title = data["title"]! as? String
answer = data["answer"] as? String
contentUrl = data["content_url"] as? String
photoUrl = data["photo_url"] as? String
createdAt = data["created_at"] as? String
feeling = data["feeling"] as? String
kind = data["kind"] as? String
text = data["text"] as? String
uid = data["uid"] as? String
measurements = data["measurements"] as? Dictionary<String, String>
}
}
I don't know exactly why but those question marks doesn't feel quite right and now and then I get some nil pointer error, which I think I should be able to avoid by using the 'good citizen' technique.
So, does anybody know how can I use Firebase following Swift best practices?
Either you wish to allow the properties of your Post class to be nil or you don't.
If you do, that's fine. The code you posted allows any of them to be nil. You just need to safely access each property every time you need it.
If you don't, then don't make them optional. Then in your init you need to ensure none of the properties are set to nil by giving each a default if there is no value in the snapshot.
class Post {
var ref: FIRDatabaseReference
var title: String
var answer: String
var contentUrl: String
var photoUrl: String
var createdAt: String
var feeling: String
var kind: String
var text: String
var uid: String
var measurements: [String : String]
//MARK: Initialization
init?(snapshot: FIRDataSnapshot) {
if let data = snapshot.value as? [String : Any] {
self.ref = snapshot.ref
title = data["title"] as? String ?? ""
answer = data["answer"] as? String ?? ""
contentUrl = data["content_url"] as? String ?? ""
photoUrl = data["photo_url"] as? String ?? ""
createdAt = data["created_at"] as? String ?? ""
feeling = data["feeling"] as? String ?? ""
kind = data["kind"] as? String ?? ""
text = data["text"] as? String ?? ""
uid = data["uid"] as? String ?? ""
measurements = data["measurements"] as? [String : String] ?? [:]
} else {
return nil
}
}
}
Note how this ensures there is a proper snapshot. Note how a default value is set to each property if there is no value in the snapshot. Obviously you can assign any default you wish. I use the empty string as an example.
Even if you want to allow the properties to be nil, you should at least update your code to check for a valid snapshot like in the code above.
Of course you can have a combination where some properties can't be nil and some can. That's up to your needs.
First it is fine for you to have optionals in your data model, as long as you assign value to it later on in the future.
I would recommend to use ObserveSingleEvent() and you should make use of completion handler to make it easy. If you don't know completion handler: Link
I recommend:
• not to put database ref in your class model, and instead of using Dictionary<String, String>? just use [String: AnyObject]?
• make your post array public so that it can be accessed into the tableview.
Here's example:
class func getPosts(uid: String, _ completion: #escaping (_ posts: [Post]?, _ error: Error?) -> Void) {
//update inside users node
var posts = [Post]()
Firebase.databaseRef.child("users").child(uid).child("posts").observeSingleEvent(of: FIRDataEventType.value, with: { (dataSnapshot) in
guard let postsDictionary = dataSnapshot.value as? [String: AnyObject] else {
completion(nil, nil)
return
}
let n = postsDictionary.count
for postDictionary in postsDictionary {
let post = Post()
post.userID = uid
if let content = postDictionary.value["content"] as? String {
post.content = content
}
if let imageURL = postDictionary.value["imageURL"] as? String {
post.imageURL = imageURL
}
if let timeStamp = postDictionary.key as String! {
if let date = timeStamp.convertToDate() {
post.timeStamp = date
}
post.postIdentifier = timeStamp
}
posts.append(post)
if posts.count == n {
// Sort the array by the newest post
let sortedPosts = posts.sorted(by: { $0.timeStamp.compare($1.timeStamp) == .orderedDescending })
completion(sortedPosts, nil)
}
}
}) { (error) in
completion(nil, error)
}
}
Assigning to tableview be like:
getPosts(uid: Current.user.userID!) { (posts, error) in
guard error == nil else {
print(error.debugDescription)
return
}
cell.label.text = posts[indexPath.item].content
Hello I am trying to parse through this json file: http://pastebin.com/TCdkJnvZ
Here is the class I made of the information I want to parse out:
public class Recipe: NSObject {
var recipeID : NSNumber?
var categoryName : String?
var ingredients : [Int : Ingredients]?
var nutrition : [Nutrition]?
var imageName : String?
var instructions : [Int : String]?
}
class Ingredients : NSObject {
var id : NSNumber?
var name : String?
var quantity: NSNumber?
var unit : String?
}
class Nutrition : NSObject {
var serving : String?
var calories : NSNumber?
var fat : String?
var carbs : NSNumber?
}
This image is the current issue.. I am really not sure what I am doing wrong here.. so if I can get any help on fixing my logic/issue it would be appreciated.
func parseToJSON(data: Any) {
// add meals to here
var recipes : [Recipe]
// single meals here
var meals : Recipe
do {
if let json = try JSONSerialization.jsonObject(with: data as! Data) as? [String: Any],
meals.recipeID == json["recipeID"] as! NSNumber? ,
meals.imageName == json["ImageURL"] as! String?,
//meals.instructions == meals.parseInstructions(instructions: (json["Instructions"] as! String)),
meals.categoryName == "Meals" ,
let ingredients = json["Ingredients"] as! [[String: Any]]? {
for items in ingredients {
var i : Int = 0
var groceryItems : Ingredients
groceryItems.id = items["IngredientID"] as? NSNumber
groceryItems.name = items["Name"] as? String
groceryItems.quantity = items["Quantity"] as? NSNumber
groceryItems.unit = items["Unit"] as? String
meals.ingredients?[i] = groceryItems
}
};
let nutritionInfo = json["NutritionInfo"] as! [[String: Any]]? {
for items in nutritionInfo {
var nutrition : Nutrition
nutrition.serving = items["SingularYieldUnit"] as? String
nutrition.calories = items["TotalCalories"] as? NSNumber
nutrition.fat = items["TotalFat"] as? String
nutrition.carbs = items["TotalCarbs"] as NSNumber
meals.nutrition = nutrition
}
};
}
catch{
}
}
It looks like you have a variety of syntax errors, but the compiler can only show one issue at a time. I've cleaned up the code slightly for you, which should push you in the right direction. I can't completely fix it because I don't know what your exact intentions are.
Here is the updated parseToJSON function:
func parseToJSON(data: Any) {
let meals = Recipe()
do {
if let json = try JSONSerialization.jsonObject(with: data as! Data) as? [String: Any] {
meals.recipeID == json["recipeID"] as! NSNumber?
meals.imageName == json["ImageURL"] as! String?
//meals.instructions == meals.parseInstructions(instructions: (json["Instructions"] as! String)),
meals.categoryName == "Meals"
if let ingredients = json["Ingredients"] as! [[String: Any]]? {
for items in ingredients {
let groceryItems = Ingredients()
groceryItems.id = items["IngredientID"] as? NSNumber
groceryItems.name = items["Name"] as? String
groceryItems.quantity = items["Quantity"] as? NSNumber
groceryItems.unit = items["Unit"] as? String
meals.ingredients?.append(groceryItems)
}
}
if let nutritionInfo = json["NutritionInfo"] as! [[String: Any]]? {
for items in nutritionInfo {
let nutrition = Nutrition()
nutrition.serving = items["SingularYieldUnit"] as? String
nutrition.calories = items["TotalCalories"] as? NSNumber
nutrition.fat = items["TotalFat"] as? String
nutrition.carbs = items["TotalCarbs"] as? NSNumber
meals.nutrition?.append(nutrition)
}
}
}
}
catch{
}
}
I also changed the Recipe object's ingredients property to:
var ingredients : [Ingredients]?
The main issue was that a lot of your code was inside of an if let expression and your indentation was off so you couldn't as easily tell.
I'm very new to iOS development and I have a problem to parse JSON response from an API.
Here is how my sample JSON looks like:
{
"recipe":{
"publisher":"Real Simple",
"f2f_url":"http://food2fork.com/view/39999",
"ingredients":[
"1 tablespoon olive oil",
"1 red onion, chopped",
"2 small yellow squash, cut into 1/2-inch pieces",
"2 cloves garlic, chopped",
"1 jalapeo, seeded and thinly sliced",
"1 kosher salt and black pepper",
"4 28-ounce can diced tomatoes\n"
],
"source_url":"http://www.realsimple.com/food-recipes/browse-all-recipes/halibut-spicy-squash-tomatoes-00000000006842/index.html",
"recipe_id":"39999", "image_url":"http://static.food2fork.com/someurl.jpg",
"social_rank":95.14721536803285,
"publisher_url":"http://realsimple.com",
"title":"Halibut With Spicy Squash and Tomatoes"
}
}
and when I print JSON (another one in this example) it looks like this:
["recipe": {
"f2f_url" = "http://food2fork.com/view/20970";
"image_url" = "http://static.food2fork.com/98113574b0.jpg";
ingredients = (
"1 (170 gram) can crabmeat",
"125 grams PHILADELPHIA Light Brick Cream Cheese Spread, softened",
"2 green onions, thinly sliced",
"1/4 cup MIRACLE WHIP Calorie-Wise Dressing",
"12 wonton wrappers"
);
publisher = "All Recipes";
"publisher_url" = "http://allrecipes.com";
"recipe_id" = 20970;
"social_rank" = "41.83825995815504";
"source_url" = "http://allrecipes.com/Recipe/Philly-Baked-Crab-Rangoon/Detail.aspx";
title = "PHILLY Baked Crab Rangoon";
}]
I have an object Recipe and it looks like this:
class Recipe {
struct Keys {
static let Title = "title"
static let ImageUrl = "image_url"
static let Ingredients = "ingredients"
static let RecipeId = "recipe_id"
}
var title : String? = nil
var id = 0
var imageUrl : String? = nil
var ingredients : String? = nil
init(dictionary : NSDictionary) {
self.title = dictionary[Keys.Title] as? String
self.id = dictionary[RecipeDB.Keys.ID] as! Int
self.imageUrl = dictionary[Keys.ImageUrl] as? String
self.ingredients = dictionary[Keys.Ingredients] as? String
}
}
and when I try to parse JSON and cast it to a dictionary I get a Could not cast value of type '__NSCFDictionary' to 'NSArray' error
here is my method that cast response to a dictionary and causes error
func recipiesFromData(data: NSData) -> [Recipe] {
var dictionary : [String : AnyObject]!
dictionary = (try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments))
as! [String : AnyObject]
let recipeDictionaries = dictionary["recipe"] as! [[String : AnyObject]]
let recipies = recipeDictionaries.map() { Recipe(dictionary: $0) }
return recipies
}
Thanks.
If that's your JSON (a single recipe), the parsing code would look like:
func recipeFromData(data: NSData) -> Recipe {
let dictionary = (try! NSJSONSerialization.JSONObjectWithData(data, options: [])) as! [String : [String : AnyObject]]
return Recipe(dictionary: dictionary["recipe"]!)
}
And I might tweak the Recipe class like so:
class Recipe {
struct Keys {
static let Title = "title"
static let ImageUrl = "image_url"
static let Ingredients = "ingredients"
static let RecipeId = "recipe_id"
}
var title: String?
var id: Int
var imageUrl: String?
var ingredients: [String]?
init(dictionary : [String : AnyObject]) {
self.title = dictionary[Keys.Title] as? String
self.id = Int(dictionary[Keys.RecipeId] as! String)!
self.imageUrl = dictionary[Keys.ImageUrl] as? String
self.ingredients = dictionary[Keys.Ingredients] as? [String]
}
}
That should parse the JSON.
Personally, I'd remove all of those ! because if there's anything wrong, it will crash. For example:
enum RecipeError: ErrorType {
case InvalidJSON(message: String, userInfo: [NSObject: AnyObject])
case MalformedJSON
case RecipeKeyNotFound
case BadKeysValues
}
func recipeFromData(data: NSData) throws -> Recipe {
var jsonObject: AnyObject
do {
jsonObject = try NSJSONSerialization.JSONObjectWithData(data, options: [])
} catch let parseError as NSError {
throw RecipeError.InvalidJSON(message: parseError.localizedDescription, userInfo: parseError.userInfo)
}
guard let dictionary = jsonObject as? [String : AnyObject] else {
throw RecipeError.MalformedJSON
}
guard let recipeDictionary = dictionary["recipe"] as? [String: AnyObject] else {
throw RecipeError.RecipeKeyNotFound
}
guard let recipe = Recipe(dictionary: recipeDictionary) else {
throw RecipeError.BadKeysValues
}
return recipe
}
Whether you go to this extreme is just a question of what sort of errors you want to be able to capture gracefully, but hopefully this illustrates the point, that you want to avoid using forced unwrapping (with ! or as!) if dealing with data that you're getting from a remote source which might introduce problems that your app must anticipate and handle gracefully rather than just crashing.
By the way, in the above example, I gave Recipe a failable initializer:
struct Recipe {
struct Keys {
static let Title = "title"
static let ImageUrl = "image_url"
static let Ingredients = "ingredients"
static let RecipeId = "recipe_id"
}
let title: String?
let id: Int
let imageUrl: String?
let ingredients: [String]?
init?(dictionary : [String : AnyObject]) {
if let idString = dictionary[Keys.RecipeId] as? String, let id = Int(idString) {
self.id = id
} else {
return nil
}
self.title = dictionary[Keys.Title] as? String
self.imageUrl = dictionary[Keys.ImageUrl] as? String
self.ingredients = dictionary[Keys.Ingredients] as? [String]
}
}
(Note, I've made this a struct, as it supports more intuitive failable initializers. If you want a failable initializer for a class, it requires that you initialize everything before you fail (which seems counter intuitive to me).
Here's valid JSON, which perfectly converts to [String : AnyObject]
let string = "{\"recipe\":{\"f2f_url\":\"http://food2fork.com/view/20970\",\"image_url\":\"http://static.food2fork.com/98113574b0.jpg\",\"ingredients\":[\"1 (170 gram) can crabmeat\",\"125 grams PHILADELPHIA Light Brick Cream Cheese Spread, softened\",\"2 green onions, thinly sliced\",\"1/4 cup MIRACLE WHIP Calorie-Wise Dressing\",\"12 wonton wrappers\"],\"publisher\":\"All Recipes\",\"publisher_url\":\"http://allrecipes.com\",\"recipe_id\":20970,\"social_rank\":\"41.83825995815504\",\"source_url\":\"http://allrecipes.com/Recipe/Philly-Baked-Crab-Rangoon/Detail.aspx\",\"title\":\"PHILLY Baked Crab Rangoon\"}}"
do{
let dict = try NSJSONSerialization.JSONObjectWithData(string.dataUsingEncoding(NSUTF8StringEncoding)!, options:NSJSONReadingOptions.AllowFragments) as! [String : AnyObject]
print(dict)
} catch let error as NSError{
print(error.localizedDescription)
}