CollectionView in TableView Cell Swift 混合卡片类型重用问题

CollectionView in TableView Cell Swift mix card type reuse issue

集合视图包含在 table 视图单元格中。 为每种卡片类型绘制集合视图单元格。 在 indexPath 0 和 1 中,卡片是单一类型的。 indexPath 2 是混合类型。 卡片类型一共有三种,live reserved vod,playLayer是vod类型的时候加上。 混合类型绘制collection view cell时,reserved类型添加playLayer,上下滚动时所有cell添加playLayer

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"
    }
}

}

我尝试的方法是让collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) -> 让 collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout.init()) 。 没有出现复用问题,但是因为改变了滚动方向,所以不能使用

您可以通过两种方式解决您的问题:

1 - 在 cellForItemAt

中重置您的单元格

目前您为每个单元格调用 cell.setupViews(with: models![indexPath.item]),这会向单元格添加子视图。由于 collectionView 重用单元格,因此在滚动时您会获得用作所有类型(直播、保留、视频点播)的单元格,这就是为什么您看到“上下滚动时将 playLayer 添加到所有单元格”的原因。

使用此代码,您还可以在每次重复使用单元格时添加子视图,这也不是最佳做法。

您可以添加一个额外的功能来重置单元格并在添加新的子视图之前删除所有子视图:

cell.subviews.forEach { [=10=].removeFromSuperview() }

(最好在cell.contentView中添加视图)

2 - 为不同的细胞类型创建单独的 类

cellFroItemAt 中,您可以检查单元格的类型和 return 所需的。每种类型都会有不同的 ReuseIdentifier 并且 playLayer 只会添加到您需要的类型中