我有以下代码显示了UITableView.
中的测验列表,问题是要显示我调用方法prepareImages
的图像,并且在填充图像时出现索引超出范围的异常tableView
函数中的单元格,因为似乎quizzesImages
数组为空(print(self.quizzesImages.count)
显示0
),我知道它与如何使线程工作有关但我看不到我要去哪里。
import UIKit
// Estructura del JSON que devuelve la URL
struct ResponseObject : Codable {
let quizzes : [Quiz]?
let pageno : Int?
let nextUrl : String?
}
class QuizzesTableViewController: UITableViewController {
// Aquí se guardan los quizzes cargados de la URL
var totalQuizzes = [Quiz]()
var quizzesImages = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = 90.0
navigationController?.navigationBar.prefersLargeTitles = true
downloadQuizzes()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func prepareImages(){
for i in 0...self.totalQuizzes.count-1{
let imageUrlString = self.totalQuizzes[i].attachment?.url
let imageUrl:URL = URL(string: imageUrlString!)!
print(imageUrl)
// Start background thread so that image loading does not make app unresponsive
DispatchQueue.global(qos: .userInitiated).async {
let imageData:NSData = NSData(contentsOf: imageUrl)!
// When from background thread, UI needs to be updated on main_queue
DispatchQueue.main.async {
let image = UIImage(data: imageData as Data)
print("hola")
self.quizzesImages.append(image!)
}
}
}
}
func downloadQuizzes(){
let QUIZZES_URL = "https://quiz2019.herokuapp.com/api/quizzes?token=945d3bf7d4c709d69940"
if let url = URL(string: QUIZZES_URL){
let queue = DispatchQueue(label: "download quizzes queue")
queue.async {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
defer{
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
let data = try? Data(contentsOf: url, options: .alwaysMapped)
let decoder = JSONDecoder()
do{
let response = try decoder.decode(ResponseObject.self, from: data!)
DispatchQueue.main.async {
if (response.quizzes!.count != 0){
self.totalQuizzes.append(contentsOf: response.quizzes!)
self.prepareImages()
print(self.totalQuizzes.count)
print(self.quizzesImages.count)
self.tableView.reloadData()
}
}
}
catch {
print(error)
}
}
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return totalQuizzes.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Quiz", for: indexPath) as! QuizTableViewCell
let quiz = totalQuizzes[indexPath.row]
let images = quizzesImages[indexPath.row]
cell.authorLabel?.text = quiz.author?.username
cell.quizLabel?.text = quiz.question
cell.quizImage?.image = images
return cell
}}
预先感谢您的帮助!
答案 0 :(得分:1)
由于您没有说异常发生在哪一行,所以我最好的猜测是它发生在这一行。
let images = quizzesImages[indexPath.row]
问题出在downloadQuizzes
中。在那儿您呼叫prepareImages
,然后几行呼叫reloadData
。由于prepareImages
执行异步任务,因此它不会阻止downloadQuizzes
完成运行。这将导致reloadData
的异步部分完成之前调用prepareImages
。
在调用prepareImages
之前,您需要确保reloadData
已完成运行。
另一种选择是使用像Kingfisher这样的库,该库仅在需要在单元格中显示图像时才下载图像,而不是全部下载并减慢显示表格的速度。
附加说明:
您不应该使用Data(contentsOf:)
加载远程内容,URLSession
是正确的选择。
答案 1 :(得分:1)
See this screen shot form my playground
我尝试在操场上运行您的代码,并设法使其进行了一些更改,请参见以下内容。
注意
要使其在操场上运行,我必须做出一些假设,只是用我的逻辑替换您的核心逻辑。
快乐编码
import UIKit
// Estructura del JSON que devuelve la URL
struct ResponseObject: Codable {
let quizzes: [Quiz]?
let pageno: Int?
let nextURL: String?
enum CodingKeys: String, CodingKey {
case quizzes, pageno
case nextURL = "nextUrl"
}
}
struct Quiz: Codable {
let id: Int?
let question: String?
let author: Author?
let attachment: Attachment?
let favourite: Bool?
let tips: [String]?
}
struct Attachment: Codable {
let filename: String?
let mime: MIME?
let url: String?
}
enum MIME: String, Codable {
case imageJPEG = "image/jpeg"
}
struct Author: Codable {
let id: Int?
let isAdmin: Bool?
let username: String?
}
class QuizzesTableViewController: UITableViewController {
// Aquí se guardan los quizzes cargados de la URL
var totalQuizzes = [Quiz]()
var quizzesImages = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = 90.0
navigationController?.navigationBar.prefersLargeTitles = true
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Quiz")
self.tableView.dataSource = self
self.tableView.delegate = self
downloadQuizzes()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func prepareImages(){
print("in prepare images")
// Start background thread so that image loading does not make app unresponsive
DispatchQueue.global(qos: .userInitiated).async {
for i in 0...self.totalQuizzes.count-1 {
let imageUrlString = self.totalQuizzes[i].attachment?.url
let imageUrl:URL = URL(string: imageUrlString!)!
print(imageUrl)
let imageData:NSData = NSData(contentsOf: imageUrl)!
let image = UIImage(data: imageData as Data)
print("hola \(i)")
self.quizzesImages.append(image!)
print(self.quizzesImages.count)
// When from background thread, UI needs to be updated on main_queue
}
DispatchQueue.main.async {
print(self.quizzesImages.count)
self.tableView.reloadData()
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
}
func downloadQuizzes(){
let QUIZZES_URL = "https://quiz2019.herokuapp.com/api/quizzes?token=945d3bf7d4c709d69940"
if let url = URL(string: QUIZZES_URL){
let queue = DispatchQueue(label: "download quizzes queue")
queue.async {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
let data = try? Data(contentsOf: url, options: .alwaysMapped)
let decoder = JSONDecoder()
do{
let response = try decoder.decode(ResponseObject.self, from: data!)
DispatchQueue.main.async {
if (response.quizzes!.count != 0){
self.totalQuizzes.append(contentsOf: response.quizzes!)
self.prepareImages()
print(self.totalQuizzes.count)
}
}
}
catch {
print(error)
}
}
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
print("num of rows \(totalQuizzes.count)")
return totalQuizzes.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("in cellForRowAt \(indexPath.row)")
let cell = tableView.dequeueReusableCell(withIdentifier: "Quiz")!
let quiz = totalQuizzes[indexPath.row]
let images = quizzesImages[indexPath.row]
cell.textLabel?.text = "\(quiz.author?.username) \(quiz.question)"
cell.imageView?.image = images
return cell
}}