UICollectionViewLayoutについて - 準備編 -

UICollectionViewのDocumentを読んだので、覚え書き程度にまとめました。

登場するメソッドで何をするのかをまとめたもので、具体的な実装方法等(コード)は一切書いていません。具体的な実装方法を知りたい方は、実装編を参照してください。

既知のことが多いと思いますので、流し読みしてください。

UICollectionViewLayoutとは

UICollectionViewCellなどを、どの位置にどんな状態で表示するかを決定するオブジェクト

UICollectionViewのLayoutを調整するものとして、

- UICollectionViewFlowLayout
- UICollectionViewDelegateFlowLayout

などがある。

その中で最も自由度の高い方法がUICollectionViewLayoutを作成するやり方。

どれを選べば良いかはCookpadさんがブログで書いてくれています。

techlife.cookpad.com

今回はこのUICollectionViewLayoutについて紹介します。

UICollectionViewを構成する視覚要素

UICollectionViewにはレイアウトが必要な3種類の視覚要素がある。

- Cells
- Supplementary views
- Decoration views

UICollectionViewは様々なタイミングでこれらの視覚要素のレイアウト情報を提供するようにLayoutオブジェクトに依頼する。

Cells

セルはレイアウトによって位置を決める主要要素。データによってはセクション毎に分けたりする。

レイアウトオブジェクトの主な仕事はUICollectionViewのViewの中身にセルを配置すること。

Supplementary Views

Header, FooterといったCellとは違うもの。これもUICollectionViewLayoutによって指定しなければならない。(optional)

Decoration Views

装飾するView。Supplementary viewsに似ているが少し違う。

こちらで場所を指定できる。(セル間の区切り線 etc..)

qiita.com

レイアウトを定める

視覚要素(Cells, Supplementary Views, Decoration Views)をどのように配置するかを決めるのが以下のメソッド。

- collectionViewContentSize
- prepare()
- layoutAttributesForElements(in:)

- layoutAttributesForItem(at:)
- layoutAttributesForSupplementaryView(ofKing: at:)
- layoutAttributesForDecorationView(ofKing: at:)

UICollectionView自体のレイアウトが変更されたり、Cellが移動・追加・削除されるたびに新しいレイアウト情報を得るために呼び出される。

collectionViewContentSize

Collection viewの全体のheight, widthを返す。

prepare()

layoutAttributesForElements(in:)は頻繁に呼び出されるため、なるべく計算処理を含まない方が良い。

そこで、レイアウト情報が更新される前に呼ばれるprepare()をOverrideして、レイアウト情報を求める計算処理を記述するのが普通である。

layoutAttributesForElements(in:)

指定された短形内の全てのセルとViewのLayoutAttributesを返す。

Cells, Supplementary views, Decoration viewsの全てのLayoutAttributesを返す必要がある。

下記のメソッドを実装することで、それぞれの要素ごとに責務を分割できる。

これらは自分で呼び出さない限り実行されない

- layoutAttributesForItem(at:)
- layoutAttributesForSupplementaryView(ofKing: at:)
- layoutAttributesForDecorationView(ofKing: at:)

自分はこれらのメソッドを使わず、自分で実装した方が楽な時の方が多い気がします。

Layout Performanceの最適化

Custom layoutを作る時、実際に変更された部分だけを無効・更新するように書くことでパフォーマンスをあげることができる。

invalidateLayout()を呼び出すことで、レイアウト情報のみを更新することができる。(reloadData()はDataSourceとレイアウトを更新する。)

更にUICollectionViewLayoutInvalidationContextのサブクラスを作成し用いることで、より無効・更新する箇所を限定することができる。

以下で最適化するのに必要なメソッドを紹介する。

- shouldInvalidateLayout(forBoundsChange:)
- invalidationContext(forBoundsChange:)
- shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)
- invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)

shouldInvalidateLayout(forBoundsChange:)

CollectionView自体のレイアウトが変更された時に、中のレイアウトを更新するかどうかを決める。

更新する時はtrue、しなくていい時はfalseを返す。

trueを返された場合はinvalidationContext(forBoundsChange:)が呼び出され、この返り値のUICollectionViewLayoutInvalidationContextを引数にinvalidateLayout(with:)を呼び出し、現在のレイアウトを無効にしてからprepare()を呼び出す。

invalidationContext(forBoundsChange:)

ここではUICollectionViewLayoutInvalidationContextを変更に応じて編集し、返す。

このContextをもとにinvalidateLayout(with:)でキャッシュしたレイアウト情報を削除する。

invalidateLayout(with:)

現在のレイアウト情報によるレイアウトを無効にする。

その際に、引数のUICollectionViewLayoutInvalidationContextの情報から更新しなければいけない箇所をを読み取り、事前に計算したレイアウト情報を修正する。(計算後にキャッシュしたレイアウト情報を削除する等々...)

shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)

Self-sizing(AutoLayout)を行うCellについて、preferredLayoutAttributesFitting(_:)で得られたLayoutAttributeslayoutAttributesForElements(in:)で指定したものと異なる場合に呼び出される。

先ほど紹介したshouldInvalidateLayout(forBoundsChange:)の引数が変ったもの。

同様に、trueを返すと更新処理に遷り、falseを返すと何もしない。

trueを返すとinvalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)が呼び出される。

Self-sizing (AutoLayout)の具体的な説明は後述する。

invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)

先ほどのinvalidationContext(forBoundsChange:)と同様のことを行う。

(アニメーションを付ける)

アニメーションをつける場合は、これらのメソッドをオーバーライドして適切なレイアウト情報を提供する必要がある。

- initialLayoutAttributesForAppearingItem(at:)
- initialLayoutAttributesForAppearingSupplementaryElement(ofKing: at:)
- initialLayoutAttributesForAppearingDecorationElement(ofKing: at:)
- finalLayoutAttributesForDisappearing~(at:)

さらに、

- finalizeCollectionViewUpdates()

をオーバーライドすることで、全体にアニメーションを追加したり、レイアウト関連の最終タスクを実装したりすることもできる。

これらについては、本記事では割愛する。

Self-Sizing (Auto Layout)

Self-SizingによってCellのSizeが変更になった時、shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)が呼び出され、レイアウトを更新するか指定できる。

developer.apple.com

引数で与えられるforPreferredLayoutAttributesは、preferredLayoutAttributesFitting(_:)の返り値、つまり、AutoLayoutによって測定されたCGSizeが含まれる。

それをキャッシュし、用いることによってUITableViewのようなSelf-Sizingを行うことができる。

具体的な実装については実装編を参照してください。

おわり。