I created a tableView with custom cells that each cell has an image.
In the model class, I created a func mainPulatesData()
to use URLSession
dataTask method to retrieve data from url, and convert data into UIImage
in the completion handler block, then add image into an variable of array of UIImage
.
The process of retrieve data and adding them into UIImage
array was perform in DispatchQueue.global(qos: .userInitiated).async
block. based on the print message, the images did be added into array.
however, even I created an instance of model class in tableView controller, and invokes the mainPulatesData()
in viewDidlLoad
, the image didn't show up in the table.
Based on other print message in table view controller class, I found even it can be added into array in model class, but it seems like doesn't work on the instance of model class in tableView controller.
that's the code in model class to gain image data:
func mainPulatesData() {
let session = URLSession.shared
if myURLs.count > 0{
print("\(myURLs.count) urls")
for url in myURLs{
let task = session.dataTask(with: url, completionHandler: { (data,response, error) in
let imageData = data
DispatchQueue.global(qos: .userInitiated).async {
if imageData != nil{
if let image = UIImage(data: imageData!){
self.imageList.append(image)
print("\(self.imageList.count) images added.")
}
}
else{
print("nil")
}
}
})
task.resume()
}
}
}
that's the code in view controller to create instance of model:
override func viewDidLoad() {
super.viewDidLoad()
myModel.mainPulatesURLs()
myModel.mainPulatesData()
loadImages()
}
private func loadImages(){
if myModel.imageList.count > 0{
tableView.reloadData()
}
else{
print("data nil")
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myModel.imageList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageTableViewCell
if myModel.imageList.count > 0{
let image = myModel.imageList[indexPath.row]
cell.tableImage = image
return cell
}
return cell
}
The reason is that the images or imageList
isn't ready by the time cellForRowAt
is called after you reloadData()
.
A good practice is to use placeholder images in the beginning and only load image when a table view cell is visible instead of everything at once. Something like:
// VC class
private var modelList = [MyModel(url: url1), MyModel(url: url2), ...]
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return modelList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageTableViewCell
cell.update(model: modelList[indexPath.row])
return cell
}
// Cell class
@IBOutlet weak var cellImageView: UIImageView!
func update(model: MyModel) {
model.fetchImage(callback: { image in
self.cellImageView.image = image
})
}
// Model class
final class MyModel: NSObject {
let url: URL
private var _imageCache: UIImage?
init(url: URL) {
self.url = url
}
func fetchImage(callback: @escaping (UIImage?) -> Void) {
if let img = self._imageCache {
callback(img)
return
}
let task = URLSession.shared.dataTask(with: self.url, completionHandler: { data, _, _ in
if let imageData = data, let img = UIImage(data: imageData) {
self._imageCache = img
callback(img)
} else {
callback(nil)
}
})
task.resume()
}
}