Chris Swinson
Chris Swinson

Reputation: 19

Error When Unwrapping Optional Element

I have an optional element, but am facing an error when displaying said element in a tableview. For reference, this element is an image, obtained from a JSON API feed. I understand that force unwrapping an element using ! is not a good idea, but it is what xcode suggests I do, giving me no other options. If I follow its suggestion, the app crashes, displaying "unexpectedly found nil while unwrapping an Optional value." Does anyone know how I can display an image from a URL. Here is the code I have thus far.

struct PlayerStats:Decodable {
    let personaname: String?
    let score: Double?
    let solo_competitive_rank: Int?
    let avatar: String?
}

The Problem is with the "avatar" element, in the cellForRowAt func.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "rankCell") as? RankTableViewCell else { return UITableViewCell() }
    cell.nameLabel.text = rank[indexPath.row].personaname

    if let imageURL = URL(string: rank[indexPath.row].avatar) {
        DispatchQueue.global().async {
            let data = try? Data(contentsOf: imageURL)
            if let data = data {
            guard let avatar = UIImage(data: data) else { return }
                DispatchQueue.main.async {
                    cell.avatarImage.image = avatar
                }
            }
        }
    }

    return cell
}

Upvotes: 0

Views: 52

Answers (2)

David Pasztor
David Pasztor

Reputation: 54716

Other than the fact that you need to safely unwrap the optional avatar property, there are several conceptual issues with your code.

First of all, you should never be performing asynchronous operations inside tableView(_:cellForRowAt:), since this is a synchronous method, so the UITableViewCell will be returned before the image could be downloaded from the network URL. You need to perform the image downloading in a separate function and reloading the table view (or at least the corresponding row/section) once the asynchronous operation finishes execution.

Secondly, don't use the initializer Data(contentsOf:) for downloading content from the internet. Even though by dispatching Data(contentsOf: imageURL), you won't be blocking the main thread, that initializer of Data shouldn't be used for downloading content from the internet. That initializer should only be used for loading content from a local file URL, not a network URL.

Upvotes: 1

vadian
vadian

Reputation: 285082

The code crashes if avatar is nil. You need to create the URL in two steps

if let avatar = rank[indexPath.row].avatar,
   let imageURL = URL(string: avatar) { ...

Alternatively decode avatar directly to URL, JSONDecoder can that

let avatar: URL?

Then you can shorten the code

if let imageURL = rank[indexPath.row].avatar {

And you are strongly discouraged from using synchronous Data(contentsOf: API as mentioned by Dávid. Use asynchronous URLSession

Upvotes: 1

Related Questions