UICollectionViewCellとUITableViewCellのレイアウト共有

ときどき、同じレイアウトのセルをテーブルビューとコレクションビューの両方で使用したくなることがあります。しかしそれぞれのセルに対応する`UITableViewCell`と`UICollectionViewCell`は別々のクラスですので、通常XIBで作成したセルのレイアウトを共有することはできません。

ただ問題はクラスの型だけですので、`UIView`を基底とするカスタムビューをXIBで作成し、これをそれぞれのセルに載せてしまえば、レイアウトを共有することができます。

![shared_cell.jpg](https://qiita-image-store.s3.amazonaws.com/0/25727/0705b0b1-cfa9-a3dc-3d64-7d77b30895a0.jpeg)

## 共通レイアウトセル(UIView)の作成

> サンプルコードはGitHubからダウンロードできます。
> https://github.com/imk2o/uicatalog

まず`UIView`を基底とするカスタムビュークラス`SharedCell`を作成します。
ただ"Cocoa Touch Class"からクラスを作成しても"Also create XIB file"が選択できないため、XIBファイルは別途追加してください。

![shared_cell_layout.jpg](https://qiita-image-store.s3.amazonaws.com/0/25727/22fa0416-9ed4-f7bf-2be7-c09d10307413.jpeg)

ここにインスタンス化メソッド`instantiateFromNib()`を実装しておきます。

```swift:SharedCell.swift
import UIKit

class SharedCell: UIView {
    @IBOutlet weak var textLabel: UILabel!
    @IBOutlet weak var imageView: UIImageView!
    
    static func instantiateFromNib() -> SharedCell {
        let nib = UINib(nibName: String(describing: self), bundle: nil)
        guard let cell = nib.instantiate(withOwner: nil, options: nil).first as? SharedCell else {
            fatalError()
        }
        
        return cell
    }
}
```

## UITableViewCell, UICollectionViewCellへの埋め込み

それぞれのカスタムセルクラスの初期化処理で`SharedCell`をインスタンス化し追加します。

* `contentView`に`addSubview()`する
* `contentView`いっぱいに表示するConstraintを付加する

を行っていれば、ちゃんとAuto LayoutやSelf sizing cellが適用されます。

```swift:SharedTableViewCell.swift
import UIKit

class SharedTableViewCell: UITableViewCell {
    var body: SharedCell? {
        return self.contentView.subviews.first as? SharedCell
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()

        // SharedCellの埋め込み
        let body = SharedCell.instantiateFromNib()
        self.contentView.addSubviewAndFitConstraints(body)
    }

    func configure(withText text: String, image: UIImage? = nil) {
        guard let body = self.body else {
            return
        }
        
        body.imageView.image = image
        body.textLabel.text = text
    }
}

extension UIView {
    func addSubviewAndFitConstraints(_ subview: UIView) {
        self.addSubview(subview)
        
        subview.translatesAutoresizingMaskIntoConstraints = false
        
        // subviewを自身いっぱいに表示するConstraintを与える
        self.addConstraint(NSLayoutConstraint(
            item: subview, attribute: .top,
            relatedBy: .equal,
            toItem: self, attribute: .top,
            multiplier: 1.0, constant: 0.0
        ))
        
        self.addConstraint(NSLayoutConstraint(
            item: subview, attribute: .left,
            relatedBy: .equal,
            toItem: self, attribute: .left,
            multiplier: 1.0, constant: 0.0
        ))
        
        self.addConstraint(NSLayoutConstraint(
            item: subview, attribute: .bottom,
            relatedBy: .equal,
            toItem: self, attribute: .bottom,
            multiplier: 1.0, constant: 0.0
        ))
        
        self.addConstraint(NSLayoutConstraint(
            item: subview, attribute: .right,
            relatedBy: .equal,
            toItem: self, attribute: .right,
            multiplier: 1.0, constant: 0.0
        ))
    }
}
```

> 各カスタムセルをStoryboardやXIB上に用意しない実装方法もありますが、イニシャライザをオーバライドしなければならなくなるため、上記の方法のほうが簡単かと思います。