Hey I'm trying to use Yelp's Review API and am having trouble structuring/writing the code necessary to display the different Yelp Star Ratings. I have no problem getting the response (it's successful). Yelp has provided image assets of all their different star ratings (5, 4.5, 4 etc. stars). Because the rating response is as a Double, I converted that into a String value. As for knowing which to call, I created an enum class so that it knows which image name to use. Using that name, I can then use it to find the image asset I need.
Now that I structure the code this way, my app crashes. Xcode will build it but upon opening the app, it crashes.
Enum class:
import Foundation
import UIKit
enum Rating: String {
case five = "regular_5"
case fourAndAHalf = "regular_4_half"
case four = "regular_4"
case threeAndAHalf = "regular_3_half"
case three = "regular_3"
case twoAndAHalf = "regular_2_half"
case two = "regular_2"
case oneAndAHalf = "regular_1_half"
case one = "regular_1"
case zero = "regular_0"
}
Yelp Client Service class:
import Foundation
import Alamofire
import SwiftyJSON
class YelpClientService {
static func getReviews(url: String, completionHandler: @escaping ([Review]?)-> Void)
{
let httpHeaders: HTTPHeaders = ["Authorization": "Bearer \(UserDefaults.standard.string(forKey: "token") ?? "")"]
//removing diacritics from the URL
if let requestUrl = URL(string: url.folding(options: .diacriticInsensitive, locale: .current))
{
Alamofire.request(requestUrl, encoding: URLEncoding.default, headers: httpHeaders).responseJSON { (returnedResponse) in
let returnedJson = JSON(with: returnedResponse.data as Any)
let reviewArray = returnedJson["reviews"].array
print(reviewArray as Any)
var reviews = [Review]()
for review in reviewArray! {
let userName = review["user"]["name"].stringValue
let ratingDouble = review["rating"].doubleValue
let rating = String(ratingDouble)
let text = review["text"].stringValue
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let timeCreated = formatter.date(from: review["time_created"].stringValue)
let url = review["url"].stringValue
let review = Review(rating: Rating(rawValue: rating)!, userName: userName, text: text, timeCreated: timeCreated!, url: url)
reviews.append(review)
}
completionHandler(reviews)
}
}
else
{
print("invalid url")
completionHandler(nil)
}
}
}
Func in View Controller thats displaying the Star:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reviewCell", for: indexPath) as! ReviewCell
let review = reviewList[indexPath.row]
print(review.userName)
cell.userName.text = review.userName
cell.reviewText.text = review.text
cell.yelpStars.image = UIImage(named: review.rating.rawValue)
//cell.date.text = review.timeCreated
return cell
}
The error when I build is: fatal error: unexpectedly found nil while unwrapping an Optional value.
I'm not sure what went wrong. Is it correct of me to instantiate rating as a Rating type? Should I keep it String?
I realize this is long code but I hope someone can help! Thank you!
I am sure it would crash. The way you have written it. let ratingDouble = review["rating"]
.doubleValue you are expecting double. It would be 0, 4.5, 3.0 etc. Which would get converted to string "0","4.5" "3.0" etc.Then you try to initialise rating with Rating(rawValue : rating)
, Rating
enum does not have these raw values as "0", "4.5" etc, so nil will be returned. You are force unwrapping it with '!", no doubt its crashing.
You will need to format your enum like this
enum Rating: String {
case five = "5.0"
case fourAndAHalf = "4.5"
case four = "4.0"
case threeAndAHalf = "3.5"
case three = "3.0"
case twoAndAHalf = "2.5"
case two = "2.0"
case oneAndAHalf = "1.5"
case one = "1.0"
case zero = "0.0"
getImageName()-> String {
switch self {
case five:
return "ImageNameForFive"
case fourAndHalf:
return "ImageNameForFourAndHalf.
......
}
}
}
and change
let rating = String(ratingDouble)
to
let rating = String.init(format: "%.1f", ratingDouble)