Reputation: 15
I have a UITable View in my program with dequeueReusableCells I should load several images from server and show them in slide show
I have a custom cell and in configuring each cell I download the images in DispatchQueue.global(qos: .userInitiated).async and in DispatchQueue.main.async I add the downloaded pic to the slide show images
but when I start scrolling some of the cells that shouldn't have any pictures , have the repeated pics of another cell
Do you have any idea what has caused this ?!
I'm using swift and also ImageSlideShow pod for the slide show in each cell
Here is some parts of my code :
In my news cell class I have below part for getting images:
class NewsCell: UITableViewCell{
@IBOutlet weak var Images: ImageSlideshow!
@IBOutlet weak var SubjectLbl: UILabel!
@IBOutlet weak var NewsBodyLbl: UILabel!
func configureCell(news: OneNews) {
self.SubjectLbl.text = news.Subject
self.NewsBodyLbl.text = news.Content
if news.ImagesId.count==0{
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
}
else{
for imgId in news.ImagesId {
let Url = URL(string: "\(BASE_URL)\(NEWS_PATH)/\(imgId)/pictures")
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: Url!)
DispatchQueue.main.async {
if let d = data {
let img = UIImage(data: data!)!
imageSrc.append(ImageSource(image: img))
self.Images.setImageInputs(imageSrc);
}
}
}
}
}
self.Images.slideshowInterval = 3
}
And this is cellForRow method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = generalNewsTableView.dequeueReusableCell(withIdentifier: "NewsCell" , for: indexPath) as! NewsCell
if let news = NewsInfo.sharedInstance.getGeneralNews(){
cell.configureCell(news: news[indexPath.row])
}
return cell
}
getGeneralNews() is a getter that returns an array of news so what I'm doing in cellForRowAt is that I get the news in the given index path and configure my cell with it .
class NewsInfo {
static var sharedInstance = NewsInfo()
private init(){}
private (set) var generalNews:[OneNews]!{
didSet{
NotificationCenter.default.post(name:
NSNotification.Name(rawValue: "GeneralNewsIsSet"), object: nil)
}
}
func setGeneralNews(allGeneralNews:[OneNews]){
self.generalNews = allGeneralNews
}
func getGeneralNews() -> [OneNews]!{
return self.generalNews
}
}
Each news contains an array of the picture Ids
These are the fields in my OneNews class var Subject :String! var Content:String! var ImagesId:[Int]!
Thanks !
Upvotes: 0
Views: 807
Reputation: 47886
You can try something like this. The main idea of this code is giving a unique number to check if the cell is reused.
NewsCell
.imageSrc
in the original definition. I assumed it was a local variable. If it was a global variable, it might lead other problems and you should avoid.(Important lines marked with ###
.)
class NewsCell: UITableViewCell {
@IBOutlet weak var images: ImageSlideshow!
@IBOutlet weak var subjectLbl: UILabel!
@IBOutlet weak var newsBodyLbl: UILabel!
//### An instance property, which holds a unique value for each cellForRowAt call
var uniqueNum: UInt32 = 0
func configureCell(news: OneNews) {
self.subjectLbl.text = news.subject
self.newsBodyLbl.text = news.content
let refNum = arc4random() //### The output from `arc4random()` is very probably unique.
self.uniqueNum = refNum //### Assign a unique number to check if this cell is reused
if news.imagesId.count==0 {
self.images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
} else {
var imageSrc: [ImageSource] = [] //###
for imgId in news.imagesId {
let Url = URL(string: "\(BASE_URL)\(NEWS_PATH)/\(imgId)/pictures")
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: Url!)
DispatchQueue.main.async {
//### At this point `self` may be reused, so check its `uniqueNum` is the same as `refNum`
if self.uniqueNum == refNum, let d = data {
let img = UIImage(data: d)!
imageSrc.append(ImageSource(image: img))
self.images.setImageInputs(imageSrc)
}
}
}
}
}
self.images.slideshowInterval = 3
}
}
Please remember, the order of images may be different than the order of imagesId
in your OneNews
(as described in Duncan C's comment).
Please try.
Upvotes: 1
Reputation: 554
If you want to give a try with this small code fix, without overriding the prepareForReuse of the cell, just change in configure cell:
if news.ImagesId.count==0{
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
}
else{
// STUFF
}
in
self.Images.setImageInputs([ImageSource(image: UIImage(named: "ImagePlaceholderIcon")!)])
if news.ImagesId.count > 0{
// STUFF
}
so every cell will start with the placeholderIcon even when reused
Upvotes: 0
Reputation: 13276
UITableViewCell are reused as you scroll. When a cell goes off the top of the screen, it will be reused for another row appearing at the bottom of the screen.
UITableViewCell has a method prepareForReuse
you can override. You can use that method to clear out iamgeViews or any other state that should be reset or cancel downloading of images.
In your case, you probably shouldn't use Data(contentsOf:)
since it doesn't give you a way to cancel it. URLSessionDataTask
would be a better option since it lets you cancel the request before it finishes.
Upvotes: 3