Search code examples
iosenumsswifty-jsonyelp

How to write swift code to display Yelp Star Images?


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!


Solution

  • 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)