今回はCompositionalLayouts
についてです
CompositionalLayoutsはiOS13から登場したCollectionViewのレイアウト構築方法です
これまではUICollectionViewFlowLayout
クラスを使用していましたが、次のような課題がありました
- Boilerplate code: ボイラープレートコード(定型的なコードで冗長になりがち)
- Performance considerations: パフォーマンスへの配慮
- Supplementary and decoration view challenges: 補足・装飾表示の課題
- Self-sizing challenges: セルフサイジングの課題
その解決策として登場したのが、CompositionalLayoutsです
CompositionalLayoutsは次のように説明されています
- Composing small layout groups together:小さなレイアウトグループをまとめて合成
- Layout groups are line-based:レイアウトグループはラインベース
- Composition instead of subclassing:サブクラス化ではなくコンポジション化
CompositionalLayoutsの構造
CompositionalLayoutsは次のように4つの構成があります
Item -> Group -> Section -> Layoutという階層構造になっています。まずは上記図のような構成を再現するようにします
NSCollectionLayoutItem
Compositional Layoutsの階層構造の最小部品のitemは生成時にNSCollectionLayoutSize
を設定する必要があります。
NSCollectionLayoutSizeというのは大まかにいうとセルのサイズです
NSCollectionLayoutSizeでサイズを指定するには次の設定値があります
- fractionalWidth、fractionalHeight: 画面サイズに対しての比率の割合を指定
- absolute: 指定した値にサイズを固定
- estimated: 値の指定はするものの優先される制約がある場合は値は変化する
次のようにサイズをして、itemは生成します
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(2/3)) let item = NSCollectionLayoutItem(layoutSize: itemSize)
上記の場合、横幅は画面一杯、縦幅は画面に対して2/3のサイズを指定しています。
absoluteやestimatedは.absolute(44)
などのように指定します
NSCollectionLayoutGroup
Groupはレイアウトの基本単位を構成します
以下3つの形で定義することが可能です
- horizontal(縦方向に並べる)
- vertical(横方向に並べる)
- custom(並べる方式をカスタマイズ)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(2/3)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
上記の場合にはデータを1つずつ縦方向に並べるという実装になります
NSCollectionLayoutSection
NSCollectionLayoutSection
はCollectionViewにおけるSectionと同じ要素です。
Groupを持ち、CompositionalLayoutsにおけるレイアウトの単位になります
NSCollectionLayoutSectionでは、次のようにGroupをSectionに追加します
let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .paging
また、section.orthogonalScrollingBehavior
でスクロールする形式を設定することができます。
.paging
で横スクロールを設定します
Layouts
Layoutsはここまでで作成したセクション単位のレイアウトを並べます
UIcollectionViewのcollectionViewLayout
に作成するレイアウトを設定します
enum SectionType: CaseIterable { case main case sub } collectionViewLayout = UICollectionViewCompositionalLayout { sectionIndex,_ -> NSCollectionLayoutSection? in let sectionLayoutKind = SectionType.allCases[sectionIndex] // sectionによって作成するセクションを変更する switch sectionLayoutKind { case .main: return self.generateApplianceHorizontalLayout() case .sub: return self.generateArticleLayout() } }
ここまで説明したものをまとめたソースコードは以下になります
import UIKit class CompositionalLayoutsCollectionView: UICollectionView { enum SectionType: CaseIterable { case main case sub } override func awakeFromNib() { super.awakeFromNib() registerNib() setupLayouts() } // セルを登録する処理を行う private func registerNib() { } private func setupLayouts() { collectionViewLayout = UICollectionViewCompositionalLayout { sectionIndex,_ -> NSCollectionLayoutSection? in let sectionLayoutKind = SectionType.allCases[sectionIndex] switch sectionLayoutKind { case .main: return self.generateApplianceHorizontalLayout() case .sub: return self.generateArticleLayout() } } } } extension CompositionalLayoutsCollectionView { private func generateApplianceHorizontalLayout() -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(2/3)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(2/3)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .paging return section } private func generateArticleLayout() -> NSCollectionLayoutSection { let fullArticleItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1/4)) let fullArticleItem = NSCollectionLayoutItem(layoutSize: fullArticleItemSize) let pairArticleItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/2), heightDimension: .fractionalHeight(1)) let pairArticleItem = NSCollectionLayoutItem(layoutSize: pairArticleItemSize) let pairGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1/4)) let pairGroup = NSCollectionLayoutGroup.horizontal(layoutSize: pairGroupSize, subitem: pairArticleItem, count: 2) let nestedGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) let nestedGroup = NSCollectionLayoutGroup.vertical(layoutSize: nestedGroupSize, subitems: [pairGroup, fullArticleItem]) let section = NSCollectionLayoutSection(group: nestedGroup) return section } }
実際にビルドしてみると次のような画面になります。最初に表示していた作成イメージと構成が一致しているのがわかると思います
アイテム間のスペース
アイテム間のスペースを開けたい場合があると思います。そんなときに設定するプロパティを少し紹介します
interGroupSpacing
interGroupSpacing
はSectionに設定するプロパティで、セクション内でのグループ間のスペースを設定します。
上下左右一律に設定した値のスペースができます
let section = NSCollectionLayoutSection(group: group) section.interGroupSpacing = 10
interItemSpacing
interItemSpacing
はGroupに設定するプロパティで、グループ内でのアイテム間のスペースを設定します。
上下左右一律に設定した値のスペースができます
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1) group.interItemSpacing = .fixed(10.0)
contentInsets
contentInsets
はGroupやSectionなど限定なしに設定できるプロパティで、スペースを設定します。
上下左右に設定した値のスペースができます
let section = NSCollectionLayoutSection(group: group) section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 10, trailing: 10)
最後に
ここまで説明しましたが、まだ完全に説明できているわけではありません。 気がついたら更新しようと思いますが、公式ドキュメントやWWWDCの動画を見たりして補いながら学習できるので、試してください!!