🎅

Compositional Layoutでgroupのsubitemの指定方法でセルの高さが微妙に変わるんじゃ〜い

2021/12/21に公開

ニフティグループ Advent Calendar 2021の20日目(遅刻)の記事です。
前日はこちらでした。

まえがき

ニフティ株式会社新卒1年目ゆるふわエンジニアのRyommです。
今年はV6の解散とSexyZone10周年に追われて良い1年でした。なんとか悪霊にならずにすみそうです。

本題

Compositional Layoutでスクロールして戻るとセルがずれる!なんでやー。

中身

itemに入れているxibに対応するクラスで、下にボーダーを引くようにしています。

SectionCollectionViewCell.swift
class SectionCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var title: UILabel!
    @IBOutlet weak var date: UILabel!
    @IBOutlet weak var label: UILabel!
    
    // セル作る時に呼び出す関数
    func generateCell(_ cellContent: SectionModel){
        title.text = cellContent.title
        date.text = cellContent.date
        
	// カテゴリーラベルの出しわけ
        if let category = cellContent.category {
            label.text = category
        } else {
            label.isHidden = true
        }
        
	// 下にボーダーをつける
        let border = CALayer()
        border.frame = CGRect(x: 0, y: self.frame.height - 1, width: self.frame.width, height: 1.0)
        border.backgroundColor = UIColor.lightGray.cgColor
        self.layer.addSublayer(border)
    }
}

xibはこんな感じ。再現のためにこんな制約になっている。
xib

ちなみにcompositional Layoutのレイアウト書く部分はこんな感じ。

ViewController.swift
private func generateSectionLayout() -> NSCollectionLayoutSection {
        // item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(CGFloat(60)))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)

        // group
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(CGFloat(60)))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
        group.contentInsets = NSDirectionalEdgeInsets(top: .zero, leading: CGFloat(20), bottom: .zero, trailing: CGFloat(20))

        // section
        let section = NSCollectionLayoutSection(group: group)

        return section
    }

これで動くはず…!
…………?

なんでやー!?
ボーダーが消える→出現は、どうもセルの描画が微妙にずれている雰囲気を感じる。。

解決法

レイアウト作成時にgroupに入れるsubitemの指定方法をsubitemsからsubitem/countにすると直るんじゃ〜

ViewController.swift
private func generateSectionLayout() -> NSCollectionLayoutSection {
        // item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(CGFloat(60)))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)

        // group
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(CGFloat(60)))
	// ↓ここ↓
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 1)
        group.contentInsets = NSDirectionalEdgeInsets(top: .zero, leading: CGFloat(20), bottom: .zero, trailing: CGFloat(20))

        // section
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: .zero, leading: .zero, bottom: .zero, trailing: .zero)

        return section
    }

たぶんこんな感じ?

subitemsとsubitem/countではサイズ決定のタイミングが違うのかな?と思います。
公式ドキュメントによると、

class func vertical(layoutSize: NSCollectionLayoutSize, subitems: [NSCollectionLayoutItem]) -> Self
Creates a group of the specified size, containing an array of items arranged in a vertical line.

class func vertical(layoutSize: NSCollectionLayoutSize, subitem: NSCollectionLayoutItem, count: Int) -> Self
Creates a group of the specified size, containing an array of equally sized items arranged in a vertical line up to the number specified by count.

とのこと。
フンフンつまりどういうことだってばよ?

たぶんこういうことだってばよ

groupには大きさの違う複数のitemを含めてネストさせることができる。
写真アプリのように複雑にネストさせたりする時はsubitemsを使う。
かわいいねこちゃん
subitemsを使うとき、itemの大きさがgroup内で異なることが想定される。
そのため、描画のたびに計算しているのでは?と思う。多分。
多分最初の描画の時に高さが確定していないんだってばよ。viewDidLayoutSubviewsで呼び出しても同じようになるから細かいことはわからないんだってばよ。
逆にsubitem/countで初めからボーダーが出ているのは、group内で単一のitemを使うから、itemの高さが早々に確定しているんだと思うんだってばよ。
自分はこう推測したけど、compositionalLayoutのライフサイクルに詳しい人の見解も聞いてみたいわね〜という気持ちはある。

さいごに

悲しいほどネタも時間もなかったので最近の発見を書いてみました。
次のアドベントカレンダーはこちらです。ぜひ

Discussion