Muhamméd Muhamméd - 2 months ago 18
Swift Question

Collection view with segment duplicate cells

I have a problem when I use collection view with segmentView.

I have a

viewController
which has a segmentView with 4 elements ( 0 => All , 1 => Photos , 2 => Audios , 3 => videos )
here's and example :

enter image description here

And I have one collectionView to display the data depend on which category clicked from the segmentController

It's working and displaying the data and here's my collectionView methods to display data

// MARK: Datasource collection method

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// All Items

if segmentStatues == 0
{
let cellForAll = collectionView.dequeueReusableCell(withReuseIdentifier: "AllItemsCell", for: indexPath) as! AllItemsCollectionViewCell

// reset elements before declare

cellForAll.cellImage.image = nil
cellForAll.playBtn.isHidden = true

cellForAll.layer.borderWidth = 1
cellForAll.layer.borderColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:1.0).cgColor
// When type is an image
if sociaPosts[(indexPath as NSIndexPath).row].postType == "image"
{

cellForAll.cellImage.sd_setImage(with: URL(string: sociaPosts[(indexPath as NSIndexPath).row].postUrl))


}else if sociaPosts[(indexPath as NSIndexPath).row].postType == "audio"
{

cellForAll.cellImage.image = UIImage(named: "recorde_icon")


}else if sociaPosts[(indexPath as NSIndexPath).row].postType == "video"
{
cellForAll.cellImage.sd_setImage(with: URL(string: sociaPosts[(indexPath as NSIndexPath).row].thumbURL))
cellForAll.playBtn.isHidden = false
}

return cellForAll

}else{

// Cell if Images or videos or audios

let newCell = collectionView.dequeueReusableCell(withReuseIdentifier: "beepbeep", for: indexPath) as! userProfileImageCollectionViewCell

// If the type is images

if segmentStatues == 1
{
// Set image URL

newCell.cellImage.sd_setImage(with: URL(string: imagesPosts[(indexPath as NSIndexPath).row].postUrl))

// Get image likes and comments and shares

newCell.likeCount.text = String(imagesPosts[(indexPath as NSIndexPath).row].likes_count)
newCell.commentCount.text = String(imagesPosts[(indexPath as NSIndexPath).row].comments_count)
newCell.shareCount.text = String(imagesPosts[(indexPath as NSIndexPath).row].shares_count)

} else if segmentStatues == 3
{
// For Video player

var player = newCell.customVid
player = Bundle.main.loadNibNamed("VieoPlayer", owner: self, options: nil)?.last as? videoPlayer

player?.newIntitVideoPlayer(forViewController: self, videoURL: videosPosts[indexPath.row].postUrl , videoThumbnail: URL(string: videosPosts[indexPath.row].thumbURL), onReady: nil)
// Set video URL and Thumbnail

// Get video likes and comments and shares
newCell.likeCount.text = String(videosPosts[(indexPath as NSIndexPath).row].likes_count)
newCell.commentCount.text = String(videosPosts[(indexPath as NSIndexPath).row].comments_count)
newCell.shareCount.text = String(videosPosts[(indexPath as NSIndexPath).row].shares_count)

}else if segmentStatues == 2
{
// Audio player
let ad = Bundle.main.loadNibNamed("AudioPlayerView", owner: self, options: nil)?.last as! AudioPlayerView
ad.frame = CGRect(x: (newCell.contentView.frame.size.width - newCell.contentView.frame.size.height * 0.6) / 2, y: 20 , width: newCell.contentView.frame.size.height * 0.6, height: newCell.contentView.frame.size.height * 0.6)
ad.playImageView.image = nil

ad.tag = 6666

newCell.contentView.addSubview(ad)

// Set audio URL
ad.audioPath = URL(string: audiosPosts[indexPath.row].postUrl)
ad.viewInitialization()

// Get Audio likes and comments and shares
newCell.likeCount.text = String(audiosPosts[(indexPath as NSIndexPath).row].likes_count)
newCell.commentCount.text = String(audiosPosts[(indexPath as NSIndexPath).row].comments_count)
newCell.shareCount.text = String(audiosPosts[(indexPath as NSIndexPath).row].shares_count)

}
return newCell
}
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

if self.segmentStatues == 0
{

return sociaPosts.count
}else if self.segmentStatues == 1
{
return imagesPosts.count
}else if self.segmentStatues == 2
{
return audiosPosts.count
}else
{
return videosPosts.count
}
}


I have 2 custom cells in my collection view one for all items and the other for (images - audios - videos ) view

my problem is data is displaying but cells duplicated when i change between segment in userProfileImageCollectionViewCell

these 2 pictures show the issue

Displaying all audios cell when i click in audio segmnet

enter image description here

But after I click in image or video segment cell duplicated like this

enter image description here

it's happen in audio - images - videos only**(userProfileImageCollectionViewCell)**

and finally here's my userProfileImageCollectionViewCell code :

updateded :

import UIKit
import SDWebImage
class userProfileImageCollectionViewCell: UICollectionViewCell {

@IBOutlet weak var customVid: videoPlayer!
@IBOutlet weak var cellImage: UIImageView!

@IBOutlet weak var likeButton: UIButton!

@IBOutlet weak var likeCount: UILabel!
@IBOutlet weak var commentButton: UIButton!

@IBOutlet weak var commentCount: UILabel!
@IBOutlet weak var shareButton: UIButton!
@IBOutlet weak var shareCount: UILabel!

override func prepareForReuse() {
cellImage.sd_setImage(with: nil)
self.customVid.subviews.forEach({ $0.removeFromSuperview() })

}
}

Answer

The issue is that UICollectionViewCell (as well as UITableViewCell by the way) are reused.

To put it simply : When a cell disappear from screen when you scroll, it may come back at the top/bottom (according to the scroll direction). But it's not a new "clean" cell, it's the previous one. That's the reuse system.

So, when you did: newCell.contentView.addSubview(ad) you added each times a new subview without checking if there was one already. The subview where juste piling. It's easy to check it with the 3D Hierarchy Debug of XCode.

Since you have a userProfileImageCollectionViewCell file, the best solution is to use prepareForReuse(). Create a IBOutlet (let's call it customVid) that will add as its subview the ad/player. In prepareForReuse() remove all the subview of customVid. In collectionView:cellForRowAtIndexPath:, do something like that:

var player = Bundle.main.loadNibNamed("VieoPlayer", owner: self, options: nil)?.last as? videoPlayer 
player?.newIntitVideoPlayer(forViewController: self, videoURL: videosPosts 
newCell.customVid.addSubview(player)

Replicate the same mechanism for the ad (you can use the same subview, it's up to you).

Now, a few suggestions not related to your issue:

Name your class starting with an uppercase: userProfileImageCollectionViewCell => UserProfileImageCollectionViewCell

I'd create methods in userProfileImageCollectionViewCell to make your code more readable. I don't speak Swift, so my next lines of code may not compile, but you should get the idea:

func fillWithVideo(videoParam video: CustomVideoClass) {
     var player = newCell.customVid
     player = Bundle.main.loadNibNamed("VieoPlayer", owner: self, options: nil)?.last as? videoPlayer
     player?.newIntitVideoPlayer(forViewController: NOTself, videoURL: video.postUrl, videoThumbnail: URL(string: video.thumbURL), onReady: nil)
    self.customVid.addSubview(player)
    // Set video URL and Thumbnail

    // Get video likes and comments and shares
     self.likeCount.text = String(video.likes_count)
     self.commentCount.text = String(video.comments_count)
     self.shareCount.text = String(video.shares_count)
}

In collectionView:cellForRowAtIndexPath:

//If it's the correct section
let video = videosPosts[(indexPath as NSIndexPath).row] as CustomVideoClass
cell fillWithVideo(video)

Note that it seems that you use the UIViewController, so you may want to add the UIViewController parameter in order to get the newIntitVideoPlayer() method to work, but I didn't want to put it in the previous example to keep it more simple. You may need to replace the lines:

cell fillWithVideo(video andViewController:self)

and

func fillWithVideo(videoParam video: CustomVideoClass andViewController viewController: UIViewController)
player?.newIntitVideoPlayer(forViewController: viewController, videoURL: video.postUrl, videoThumbnail: URL(string: video.thumbURL), onReady: nil)

Etc. for each part specific part. It make just easier to read what you are doing. Handle only in collectionView:cellForRowAtIndexPath: the logic (which section should get which item, etc.) and the rest to the cell that may adapt.

Comments