Jane
Jane

Reputation: 15

CollectionView in TableView Cell Swift mix card type reuse issue

A collection view is contained in a table view cell. Collection view cells are drawn for each card type. In indexPath 0 and 1, the card is of a single type. indexPath 2 is a mix type. There are three card types, live reserved vod, and playLayer is added when it is a vod type. When drawing collection view cells in mix type, playLayer is added to reserved type, and playLayer is added to all cells when scrolling up and down.

class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

lazy private var homeManager = HomeManager()
var sections: [Section]?
var liveData: [Item]?
var vodData: [Item]?
var mixData: [Item]?

var table: UITableView = {
   let tableView = UITableView()
    tableView.register(CardCollectionTableViewCell.self, forCellReuseIdentifier: CardCollectionTableViewCell.identifier)
   return tableView
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .black
    view.addSubview(table)
    homeManager.getdata{ [weak self] response in
        self?.sections = response.sections ?? []
        self?.liveData = self?.sections?[1].items ?? []
        self?.vodData = self?.sections?[2].items ?? []
        self?.mixData = self?.sections?[3].items ?? []
        
        self?.table.reloadData()
    }
    
    table.delegate = self
    table.dataSource = self
    
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    table.frame = view.bounds
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 3
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        if indexPath.row == 0 && liveData != nil {
            let cell = table.dequeueReusableCell(withIdentifier: CardCollectionTableViewCell.identifier, for: indexPath) as! CardCollectionTableViewCell
            cell.configure(with: liveData)
            cell.titleLabel.text = sections![1].title!
            return cell
        }  else if indexPath.row == 1 && vodData != nil {
            let cell = table.dequeueReusableCell(withIdentifier: CardCollectionTableViewCell.identifier, for: indexPath) as! CardCollectionTableViewCell
            cell.configure(with: vodData)
            cell.titleLabel.text = sections![2].title!
            return cell
        } else if indexPath.row == 2 && mixData != nil {
            let cell = table.dequeueReusableCell(withIdentifier: CardCollectionTableViewCell.identifier, for: indexPath) as! CardCollectionTableViewCell
            cell.configure(with: mixData)
            cell.titleLabel.text = sections![3].title!
           
            return cell
        }
        
        else {
            return UITableViewCell()
        }

}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 400
}

CardCollection TableViewCell

static let identifier = "CardCollectionTableViewCell"

var titleLabel: UILabel = {
    let label = UILabel()
    label.font = Fonts.text16()
    label.textColor = .white
    return label
}()

 var collectionView: UICollectionView = {
    let layout = UICollectionViewFlowLayout()
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    layout.scrollDirection = .horizontal
    collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: MyCollectionViewCell.identifier)
    collectionView.backgroundColor = .black
    return collectionView
}()

var models:[Item]?

func configure(with models: [Item]?) {
    
    self.models = models
    
    titleLabel.snp.makeConstraints { make in
        make.top.equalToSuperview()
        make.leading.equalToSuperview().offset(20)
        make.width.equalTo(300)
        make.height.equalTo(24)
    }
    
    collectionView.snp.makeConstraints { make in
        make.top.equalToSuperview().offset(44)
        make.leading.equalToSuperview().offset(20)
        make.trailing.bottom.equalToSuperview()
    }
    
    collectionView.reloadData()
}

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    contentView.addSubview(collectionView)
    contentView.addSubview(titleLabel)
    
    collectionView.delegate = self
    collectionView.dataSource = self
    
    contentView.backgroundColor = .black
    
    //guard models != nil else { return }
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
// Collectionview

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return models?.count ?? 5
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.identifier, for: indexPath) as! MyCollectionViewCell
    cell.setupViews(with: models![indexPath.item])
    cell.setupConstraints(with: models![indexPath.item])
    cell.configure(with: models![indexPath.item])
    return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: 140, height: 350)
}

CollectionViewCell

class MyCollectionViewCell: UICollectionViewCell {

static let identifier = "MyCollectionViewCell"

var player: AVPlayer?

private lazy var imageView: UIImageView = {
    let image = UIImageView()
    image.contentMode = .scaleAspectFill
    image.clipsToBounds = true
    image.backgroundColor = .blue
    image.layer.cornerRadius = 8
    return image
}()

private lazy var typeLabelBackgroud: UIImageView = {
    let image = UIImageView()
    image.clipsToBounds = true
    image.layer.cornerRadius = 8
    return image
}()

private lazy var playerView: AVPlayerLayer? = {
   let url = URL(string: "https://1303309272.vod2.myqcloud.com/7e895809vodkr1303309272/8155555e3701925920462082823/f0.mp4")
    player = AVPlayer(url: url!)
    let playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = CGRect(x: 0, y: 0, width: 140, height: 210)
    playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        self.player?.play()
    }
   
    return playerLayer
}()

private lazy var typeLabel: UILabel = {
    let label = UILabel()
    label.font = Fonts.text10()
    label.textColor = .white
    return label
}()

private lazy var timeLabel: UILabel? = {
    let label = UILabel()
    label.font = Fonts.text11()
    label.textColor = .white
    label.frame.size.width = 42
    label.frame.size.height = 16
    return label
}()

private lazy var titleLabel: UILabel = {
    let label = UILabel()
    label.numberOfLines = 0
    label.font = Fonts.text14()
    label.textColor = .white
    return label
}()

private lazy var storeName: UILabel = {
    let label = UILabel()
    label.font = Fonts.text11()
    label.textColor = .gray
    return label
}()

private lazy var heartImage: UIImageView = {
    let image = UIImageView()
    image.image = UIImage(named: "Vector")
    image.contentMode = .scaleAspectFit
    return image
}()

private lazy var heartCountLabel: UILabel = {
    let label = UILabel()
    label.font = Fonts.text11()
    label.textColor = .gray
    label.frame.size.width = 27
    label.frame.size.height = 16
    label.textAlignment = .left
    return label
}()

private lazy var eyeImage: UIImageView = {
    let image = UIImageView()
    image.image = UIImage(named: "Eye")
    image.contentMode = .scaleAspectFit
    return image
}()

private lazy var eyeCountLabel: UILabel = {
    let label = UILabel()
    label.font = Fonts.text11()
    label.textColor = .gray
    label.frame.size.width = 27
    label.frame.size.height = 16
    label.textAlignment = .left
    return label
}()

private override init(frame: CGRect) {
    super.init(frame: frame)
    
}

public convenience init() {
    self.init(frame:. zero)

}

required init?(coder: NSCoder) {
  
    
    fatalError("init(coder:) has not been implemented")
}

 func setupViews(with model: Item?) {
guard model != nil else {return}
print("CollectionViewCell의 model! data: \(model)")
    if model!.type == "LIVE" {
       addSubview(imageView)
       addSubview(typeLabelBackgroud)
       addSubview(typeLabel)
       addSubview(titleLabel)
       addSubview(storeName)
       addSubview(heartImage)
       addSubview(heartCountLabel)
       addSubview(eyeImage)
       addSubview(eyeCountLabel)
    } else if model!.type == "RESERVED"{
        addSubview(imageView)
        addSubview(typeLabelBackgroud)
        addSubview(typeLabel)
        addSubview(titleLabel)
        addSubview(storeName)
        addSubview(heartImage)
        addSubview(heartCountLabel)
        addSubview(eyeImage)
        addSubview(eyeCountLabel)
    }  else if model!.type == "VOD" {
       addSubview(imageView)
       addSubview(typeLabelBackgroud)
       addSubview(typeLabel)
       addSubview(titleLabel)
       addSubview(storeName)
       addSubview(heartImage)
       addSubview(heartCountLabel)
       addSubview(eyeImage)
       addSubview(eyeCountLabel)
       addSubview(timeLabel!)
        imageView.layer.addSublayer(playerView!)
    }

}

 func setupConstraints(with model: Item?) {
 guard model != nil else {return}
    if model!.type == "LIVE" {
        imageView.snp.makeConstraints { make in
            make.width.equalTo(140)
            make.height.equalTo(210)
        }
        
        typeLabelBackgroud.snp.makeConstraints { make in
            make.leading.equalTo(imageView).inset(8)
            make.top.equalTo(imageView).inset(10)
            make.width.equalTo(33)
            make.height.equalTo(20)
        }
        
        typeLabel.snp.makeConstraints { make in
            make.leading.equalTo(typeLabelBackgroud).inset(6)
            make.top.equalTo(typeLabelBackgroud).inset(2)
            make.width.equalTo(21)
            make.height.equalTo(16)
        }
        
        titleLabel.snp.makeConstraints { make in
            make.top.equalTo(imageView.snp.bottom).offset(8)
            make.width.equalTo(140)
            make.height.equalTo(42)
        }
        
        storeName.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(4)
            make.width.equalTo(140)
            make.height.equalTo(16)
        }
        
        heartImage.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.width.height.equalTo(16)
        }
        
        heartCountLabel.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.leading.equalTo(heartImage.snp.trailing).offset(5)
        }
        
        eyeImage.snp.makeConstraints { make in
            make.leading.equalTo(heartCountLabel.snp.trailing).offset(13)
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.width.height.equalTo(16)
        }
        
        eyeCountLabel.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.leading.equalTo(eyeImage.snp.trailing).offset(5)
        }
        
    } else if model!.type == "RESERVED" {
        imageView.snp.makeConstraints { make in
            make.width.equalTo(140)
            make.height.equalTo(210)
        }
        
        typeLabelBackgroud.snp.makeConstraints { make in
            make.leading.equalTo(imageView).inset(8)
            make.top.equalTo(imageView).inset(10)
            make.width.equalTo(33)
            make.height.equalTo(20)
        }
        
        typeLabel.snp.makeConstraints { make in
            make.leading.equalTo(typeLabelBackgroud).inset(6)
            make.top.equalTo(typeLabelBackgroud).inset(2)
            make.width.equalTo(21)
            make.height.equalTo(16)
        }
        
        titleLabel.snp.makeConstraints { make in
            make.top.equalTo(imageView.snp.bottom).offset(8)
            make.width.equalTo(140)
            make.height.equalTo(42)
        }
        
        storeName.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(4)
            make.width.equalTo(140)
            make.height.equalTo(16)
        }
        
        heartImage.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.width.height.equalTo(16)
        }
        
        heartCountLabel.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.leading.equalTo(heartImage.snp.trailing).offset(5)
        }
        
        eyeImage.snp.makeConstraints { make in
            make.leading.equalTo(heartCountLabel.snp.trailing).offset(13)
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.width.height.equalTo(16)
        }
        
        eyeCountLabel.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.leading.equalTo(eyeImage.snp.trailing).offset(5)
        }
    } else if model!.type == "VOD" {
        imageView.snp.makeConstraints { make in
            make.width.equalTo(140)
            make.height.equalTo(210)
        }
        
        typeLabelBackgroud.snp.makeConstraints { make in
            make.leading.equalTo(imageView).inset(8)
            make.top.equalTo(imageView).inset(10)
            make.width.equalTo(33)
            make.height.equalTo(20)
        }
        
        typeLabel.snp.makeConstraints { make in
            make.leading.equalTo(typeLabelBackgroud).inset(6)
            make.top.equalTo(typeLabelBackgroud).inset(2)
            make.width.equalTo(21)
            make.height.equalTo(16)
        }
        
        timeLabel!.snp.makeConstraints { make in
            make.top.equalTo(imageView).inset(8)
            make.trailing.equalTo(imageView).inset(10)
           
        }
        
        titleLabel.snp.makeConstraints { make in
            make.top.equalTo(imageView.snp.bottom).offset(8)
            make.width.equalTo(140)
            make.height.equalTo(42)
        }
        
        storeName.snp.makeConstraints { make in
            make.top.equalTo(titleLabel.snp.bottom).offset(4)
            make.width.equalTo(140)
            make.height.equalTo(16)
        }
        
        heartImage.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.width.height.equalTo(16)
        }
        
        heartCountLabel.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.leading.equalTo(heartImage.snp.trailing).offset(5)
        }
        
        eyeImage.snp.makeConstraints { make in
            make.leading.equalTo(heartCountLabel.snp.trailing).offset(13)
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.width.height.equalTo(16)
        }
        
        eyeCountLabel.snp.makeConstraints { make in
            make.top.equalTo(storeName.snp.bottom).offset(3)
            make.leading.equalTo(eyeImage.snp.trailing).offset(5)
        }
    }
}

    public func configure(with model: Item?) {
    //self.model! = model!
    
     guard model != nil else {return}
    if model!.type == "LIVE" {
        imageView.kf.setImage(with: URL(string: model!.image!))
        typeLabel.text = "LIVE"
        typeLabelBackgroud.backgroundColor = .orange
        titleLabel.text = String(model!.title!)
        storeName.text = String(model!.store!)
        heartCountLabel.text = String(model!.likeCount!)
        eyeCountLabel.text = String(model!.playedCount!)
    } else if model!.type == "RESERVED" {
        imageView.kf.setImage(with: URL(string: model!.image!))
        typeLabel.text = "예정"
        typeLabelBackgroud.backgroundColor = Colors.primary()
        titleLabel.text = String(model!.title!)
        storeName.text = String(model!.store!)
        heartCountLabel.text = String(model!.likeCount!)
        eyeCountLabel.text = String(model!.playedCount!)
    } else if model!.type == "VOD" {
        imageView.kf.setImage(with: URL(string: model!.image!))
        typeLabel.text = "VOD"
        titleLabel.text = String(model!.title!)
        typeLabelBackgroud.backgroundColor = Colors.vodBackgroud()
        storeName.text = String(model!.store!)
        heartCountLabel.text = String(model!.likeCount!)
        eyeCountLabel.text = String(model!.playedCount!)
        timeLabel?.text = "01:35:40"
    }
}

}

The way I tried is let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) -> let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout.init()) . The reuse problem did not occur, but it cannot be used because the scroll direction is changed.

Upvotes: 0

Views: 932

Answers (1)

Viktor Gavrilov
Viktor Gavrilov

Reputation: 976

You could solve your problem in two ways:

1 - reset your cell in cellForItemAt

Currently you call cell.setupViews(with: models![indexPath.item]) for each cell, which adds subviews to the cells. Since collectionView reuses cells, when scrolling you are getting cells which where used as all types (live, reserved, vod), that is why you see that "playLayer is added to all cells when scrolling up and down".

With this code you also add subviews each time the cell is reused, which is also not the best practice.

You could add an additional function to reset the cell and remove all subviews before adding new ones:

cell.subviews.forEach { $0.removeFromSuperview() }

(it is also better to add views to the cell.contentView)

2 - Create separate classes for different cell types

In cellFroItemAtyou could check for the type of the cell and return the required on. Each type will have a different ReuseIdentifier and playLayer will be added only to the type you need

Upvotes: 0

Related Questions