Reputation: 19
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
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
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