淺談UICollectionViewCompositionalLayout 的結構與如何閱讀

Michael Revlis
7 min readJan 18, 2022

iOS 13 以後我們可以用 UICollectionViewCompositionalLayout 來更輕鬆地做到某些畫面的呈現,算是便利開發者的一大福音,最大的亮點大概就是:

  • 易於閱讀的 layout 設定,先見林再見樹,算是 DX 的一個優化
    過往的 UICollectionViewDelegateFlowLayout 是功能集中設定的,例如所有的 section inset 都放在一起、所有的 header 放在一起。但是現實中我們可能更在意的是某個 section 他自己的 inset、header 設定,使用 compositional layout 就不用在多個 delegate 間反覆跳著看才能看到全貌,可以聚焦在某個目標區塊的呈現。
  • 在一個垂直滑動的 CollectionView 中實踐左右滑動的內容
    終於不用在 CollectionViewCell 中再塞一個 CollectionView 了,不只結構扁平化,資料與行為的傳遞也扁平了。
本篇只會簡單講到 compositional layout 的基礎結構、可以做到什麼、該如何閱讀它。其餘更細節的探究會陸續用別的篇幅說明。

接下來會直接帶大家看 code,以下是一個簡單的 UICollectionView 設定,閱讀上建議 從下面往上 開始看、從大的畫面看到小的collection view -> collection view layout -> section -> group -> item -> size, 但在 init 的結構上則會是反過來由小到大。如果是複雜的結構可能還會有多個 section、group,但只要好好的命名,就能幫助未來維護的人(也可能就是自己)快速找到對應的畫面區塊。也由於這樣的組成結構,讓我們在開發、閱讀的時候可以聚焦在某個 section,或某個 group 上,而不用在多個 delegate 中穿梭,再配上 switch-case, if-else 才能找到某個特定的畫面,所有跟某個區塊相關的設定都集中在這裡。

Demo code 1

在第 12 行我們先用 UICollectionViewCompositionalLayout 來初始化我們的 UICollectionView。往上看到第 11 行,發現 compositional layout 的 init 是需要先設定 NSCollectionLayoutSection 的。Demo code 1 中這個 collection view 中有一個 section,但如果我們想要多個 section 呢?這邊就直接往下看到官方的文件,原來除了單個 section 的 init 外,還可以透過 sectionProvider 來創建多個 sections 的設定,甚至如果希望 CollectionView 長得像 TableView 的話,還可以使用 list(using:),詳細的討論之後再另外寫一篇補上。

Apple Developer Documentation — UICollectionViewCompositionalLayout
Apple Developer Documentation — UICollectionViewCompositionalLayout

接著看到第 10 行 NSCollectionLayoutSection 的初始化。這邊看到了不一樣的東西:NSCollectionLayoutGroup,這個就是 compositional layout 引入的新構想,在 section 與 item 中插入了 group 這一個階層,由 group 來決定 item 的呈列方式,藉此達到垂直滑動的 CollectionView 也可以有左右滑動的內容,有別於以往 item 的呈列方式是跟著 CollectionView 是一起的。更進階一點,我們甚至可以 item 和 group 一起塞到另一個 group 中,以做到更複雜的排列。

Apple Developer Documentation — UICollectionViewCompositionalLayout

第 7 行我們來看看這個 NSCollectionLayoutGroup,從官方文件可以看到他有五個 init 方式,分別是可以決定它的排版是水平、垂直或自訂。這裡我們可以從 Xcode 的文件看到官方圖文並茂的解釋,且原來水平、垂直排版還可以指定一個 group 要被多少個 item 去均分!省去我們手動計算一屏畫面中要顯示多少個 item。

Xcode — NSCollectionLayoutGroup

舉個例子,在 Demo code 2 中,我們想要讓畫面上一次呈現兩個半的 items(假設 UICollectionView 是滿版、item 之間也沒有 spacing),option1 比較是傳統上我們會寫的:指定 item 的寬度是 collection view 總寬的 1/3(這裡因為是用 compositional layout,所以嚴格來說是 group 的 1/3);但我們也可以考慮 option2horizontal(layoutSize:, subitem:, count:) 並指定 count: 3。雖然結果一樣,但在閱讀上還是有差異的,看各位開發者如何取捨。另外值得一提的是這兩個 compositional layout 的做法因為都是讓 item 去跟 group 作對齊,而 group 真正的大小又是它自己的 contentInsets、其 section 的 contentInsets、interGroupSpacing 等設定得結果。跟過去可能要在 collectionView(_:, layout:, sizeForItemAt:) 中自己手動計算 item 寬方面簡潔許多,不用費力找出對應的 section 和當前 item 的 contentInsets、spacing 再一一減去,或有一堆 magic number 造成維運與閱讀上的困擾。

Demo code 2

回到 Demo code 1 第六行的 NSCollectionLayoutItem 就是整個 collection view 中最小最基礎的畫面元件,可以是 cell、header、footer。且我們可以指定 item 的 size,分別針對寬、高設定,看是要指定一個絕對值 .absolute(44);想要內容可以 run time 改變的就用 .estimated(44);如果是要對齊其容器(也就是 group)寬就用 .fractionalWidth(0.2);要對齊容器高 .fractionalHeight(0.2)

以上就是一個 compositional layout 的基本結構介紹。Demo code 1 的內容實作出來跟一個一般的 UICollectionView 其實是一樣的,並沒有用到新的花招 XD 但也是想藉此讓大家看看最單純的 compositional layout 會怎麼寫、又該怎麼閱讀它。

--

--