Help us understand the problem. What is going on with this article?

UICollectionViewCompositionalLayoutでWaterfall(Pinterest風)レイアウトを実現する

iOS 13が登場して1年が経過しました。(もうiOS 14も出ますね)
昨年登場した物としてSwiftUIやCombineは注目度が高かったですがUICollectionViewCompositionalLayoutも忘れてはいけない存在です。
UICollectionViewCompositionalLayoutは簡単に柔軟なレイアウトを構築することができるので使い始めるとかなりの画面で役に立ちます。
しかし、少し凝ったレイアウトをしようと思うとどうすればいいのか分からなかったので、今回はPinterest風のレイアウトをどう実現するのかを考えてみました。

Waterfallレイアウト(Pinterest風レイアウト)とは

名称未設定.png

Waterfallレイアウトはこのように上からサイズが異なるコンポーネントが上から積み重なっているようなレイアウトのことを言います。
水が上から下に落ちるように見えるのでWaterfall(滝)ということですね。

レイアウトの考え方

レイアウトを作る際に重要なのは次の2点です。

  • カラム数(横に何列表示するか)
  • それぞれのセルのサイズが明確なこと

上記が分かっていればカラムごとに高さを足していけばそれっぽいレイアウトが作れます。

実装サンプル

protocol WaterfallLayoutDelegate: AnyObject {
    func numberOfColumns() -> Int
    func columnsSize(at indexPath: IndexPath) -> CGSize
    func columnSpace() -> CGFloat
}

lazy var collectionViewLayout: UICollectionViewCompositionalLayout = {
    return UICollectionViewCompositionalLayout { [unowned self] (section, environment) -> NSCollectionLayoutSection? in
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(environment.container.effectiveContentSize.height))
        let group = NSCollectionLayoutGroup.custom(layoutSize: groupSize) { [unowned self] (environment) -> [NSCollectionLayoutGroupCustomItem] in
            var items: [NSCollectionLayoutGroupCustomItem] = []
            var layouts: [Int: CGFloat] = [:]
            let space: CGFloat = self.waterfallLayout.flatMap({ CGFloat($0.columnSpace()) }) ?? 1.0
            let numberOfColumn: CGFloat = self.waterfallLayout.flatMap({ CGFloat($0.numberOfColumns()) }) ?? 2.0
            let defaultSize = CGSize(width: 100, height: 100)

            (0 ..< self.collectionView.numberOfItems(inSection: section)).forEach {
                let indexPath = IndexPath(item: $0, section: section)

                let size = self.waterfallLayout?.columnsSize(at: indexPath) ?? defaultSize
                let aspect = CGFloat(size.height) / CGFloat(size.width)

                let width = (environment.container.effectiveContentSize.width - (numberOfColumn - 1) * space) / numberOfColumn
                let height = width * aspect

                let currentColumn = $0 % Int(numberOfColumn)
                let y = layouts[currentColumn] ?? 0.0 + space
                let x = width * CGFloat(currentColumn) + space * (CGFloat(currentColumn) - 1.0)

                let frame = CGRect(x: x, y: y + space, width: width, height: height)
                let item = NSCollectionLayoutGroupCustomItem(frame: frame)
                items.append(item)

                layouts[currentColumn] = frame.maxY
            }
            return items
        }
        return NSCollectionLayoutSection(group: group)
    }
}()

大事なところだけ抜き出しておきました。
フルのサンプルコードは以下のGistに置いています。

fromkk/WaterfallLayout.swift

まとめ

NSCollectionLayoutGroup.customを利用することで柔軟なレイアウトも簡単に構築することができました。
NSCollectionLayoutGroupCustomItemframeの他にもzIndexを指定することができるので、色々と柔軟なレイアウトが作れそうですね。

fromkk
iOSアプリエンジニアです。 # Type( https://type-markdown.app )やPity( https://freetimepicker.firebaseapp.com/ )というMarkdownエディタアプリを個人的に開発しています。
http://note.com/fromkk
engineerlife
技術力をベースに人生を謳歌する人たちのコミュニティです。
https://community.camp-fire.jp/projects/view/280040
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした