🎃

TableViewCellにCollectionViewを配置するメモ

2021/01/24に公開

TableViewCell内にCollectionViewを配置する際に、処理の移譲先で少し悩んだので備忘録として残しておきます。
基本的には、TableViewCell内のCollectionViewを配置した場合でも、ViewControllerに直接CollectionViewを配置した場合と考え方は変わらず、ViewControllerにdelegateとdataSorceの責務を移譲します。
また、CollectionViewCell内にUIButtonを設置する場合は、CollectionViewCellDelegateのようなprotocolを宣言して、ViewController側でそのprotocolに準拠してあげれば、CollectionViewCell内のボタンタップイベントをViewControllerで実装できます。

以下、簡単なデモ用のコード。今回はcollectionViewCell内にimageViewとUIButtonを設置して、UIButtonの処理をViewControllerに移譲するようにしています。

Card.swift
import Foundation

struct Card: Codable {
    var id: Int?
    var title: String?
    var imageUrl: String?
}
CustomTableViewCell.swift

import UIKit

class CustomTableViewCell: UITableViewCell {

    @IBOutlet weak var collectionView: UICollectionView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        let customCollectionViewCellName = "CustomCollectionViewCell"
        collectionView.register(UINib(nibName: customCollectionViewCellName, bundle: nil), forCellWithReuseIdentifier: customCollectionViewCellName)
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
}

CustomCollectionViewCell.swift

import UIKit

protocol CustomCollectionViewCellDelegate: AnyObject {
    func showDetail(cell: CustomCollectionViewCell)
}

class CustomCollectionViewCell: UICollectionViewCell {
    weak var delegate: CustomCollectionViewCellDelegate?
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var imageview: UIImageView!
    
    @IBAction func tappedButton(_ sender: Any) {
        delegate?.showDetail(cell: self)
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    func configure(card: Card) {
        titleLabel.text = card.title
        let image = getImageByUrl(url: card.imageUrl!)
        imageview.image = image
    }
    
    func getImageByUrl(url: String) -> UIImage{
        let url = URL(string: url)
        do {
            let data = try Data(contentsOf: url!)
            return UIImage(data: data)!
        } catch let err {
            print("Error : \(err.localizedDescription)")
        }
        return UIImage()
    }
}
ViewController.swift
import UIKit

class ViewController: UIViewController {
    var cards: [Card] = []

    @IBOutlet weak var tableView: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        initTable()
        
        // 今回はdemoのためcards配列に適当な値を入れてしまう。
        cards.append(Card(id: 1, title: "test", imageUrl: "https://www.pakutaso.com/shared/img/thumb/susipaku211-app90962_TP_V.jpg"))
        tableView.reloadData()
    }
    
    func presentToDetail(card: Card) {
        // カード詳細画面への遷移処理。
        // 例えば、Card.idをパラメータとしてwebView開くなど。
        print("presentToDetail")
    }
}


// MARK: UITableViewDelegate, UITableViewDataSource

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func initTable() {
        tableView.delegate = self
        tableView.dataSource = self
        let customTableViewCellName = "CustomTableViewCell"
        tableView.register(UINib(nibName: customTableViewCellName, bundle: nil), forCellReuseIdentifier: customTableViewCellName)
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath as IndexPath) as! CustomTableViewCell
        cell.collectionView.delegate = self
        cell.collectionView.dataSource = self
        return cell
    }
    
    // cellの高さ。今回はdemoのため適当な数値。
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 200
    }
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        cards.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath as IndexPath) as! CustomCollectionViewCell
        cell.configure(card: cards[indexPath.row])
        cell.delegate = self // CustomCollectionViewCellDelegate
        return cell
    }
}

extension ViewController: CustomCollectionViewCellDelegate {
    func showDetail(cell: CustomCollectionViewCell) {
        // collectionViewCoellのindexPathを特定するために、まずはtableViewCellを特定する。
        // 今回の場合は、tableViewCellは一つしかない想定なので、決め打ちでIndexPath(row: 0, section: 0)を指定。
        guard let tableViewCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? CustomTableViewCell, let indexPath = tableViewCell.collectionView.indexPath(for: cell) else { return }
        presentToDetail(card: cards[indexPath.row])
    }
}

以上。

Discussion