T.Tehranchi
T.Tehranchi

Reputation: 15

Pictures getting Mixed when scrolling in UITable View Swift

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

Answers (3)

OOPer
OOPer

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.

  • I have renamed many properties in your code, as Capitalized identifiers for non-types make the code hard to read. You cannot just replace whole definition of your original NewsCell.
  • There was no declaration for 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

MarkWarriors
MarkWarriors

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

Craig Siemens
Craig Siemens

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

Related Questions