2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

iOSでセルが下から上に向かって表示されるリストの作成方法について検討してみた。実装例は以下のリポジトリにある↓

各方法の最後にも該当するコードへのリンクを記載している。

方法1 リスト全体を反転させる

一番簡単な方法。

UICollectionViewUITableViewtransformで上下反転し、表示するセルも同様に反転させることで、下から上に向かって表示されるように見せる。

collectionView.transform = CGAffineTransform(scaleX: 1, y: -1)
let cell = collectionView.dequeueConfiguredReusableCell(
    using: cellRegistration,
    for: indexPath,
    item: collectionView.numberOfItems(inSection: indexPath.section) - indexPath.item - 1
)
cell.transform = CGAffineTransform(scaleX: 1, y: -1)

この方法は新たに下に要素が追加されると常に要素全体が上がっていく。

そのため、ユーザーがスクロールしている間に要素がどんどん追加される場合、操作中に要素の位置が変わるためユーザー操作を阻害する可能性がある。

サンプル↓

方法2 リストの上部にInsetを作る

方法1と異なり要素全体が上がらない比較的簡単な方法。

UICollectionViewUITableViewcontentSize.heightが表示領域より小さい間、contentSize.heightがそのviewのサイズになるように上部のマージンを調整することで、下から上に向かって表示されるように見せる。

サンプルではCombineを使ってcontentSizeを監視し、サイズが変化した時にUICollectionViewの上部と親UIViewとの余白を調整している。

collectionView.publisher(for: \.contentSize).map(\.height).removeDuplicates().sink { [weak self] contentHeight in
    guard let collectionViewTopConstraint = self?.collectionViewTopConstraint,
          let containerViewBoundsHeight = self?.containerView.bounds.height
    else { return }
    collectionViewTopConstraint.constant = max(containerViewBoundsHeight - contentHeight, 0)
}
.store(in: &cancellables)

この方法だと下に要素が追加されても要素全体が上がっていくことはない。

代わりに最下部で固定したい場合は、要素が追加される度contentOffsetの調整が必要になってくる。

サンプル↓

方法3 UICollectionViewLayoutをカスタムする

一番面倒だが一番自由度が高い方法。

UICollectionViewFlowLayoutUICollectionViewLayoutを継承したクラスを作り、自分でレイアウトを実装する。

Customizing Collection View Layouts

今回は簡単にUICollectionViewFlowLayoutを使って高さ固定を前提に作成した。
セル全ての高さcontentSize.heightが表示領域より小さい場合、表示領域を座標系にして下部詰めで各セルを並べるようにしている。
(本来prepare()で諸々の計算をすべきだが、ここでは極力シンプルにするため省略している)

サンプルでは方法2と同じように下に要素が追加されても要素全体が上がらないように実装しているが、方法1と同じように作ることも可能なはずだ。

サンプル↓

番外編 方法1の方法を使ったSwiftUIでの実装方法

方法1のView全体を反転する方法はSwiftUIでも使える。

以下のようにScrollViewListなどをrotationEffect(_:anchor:)を使って上下反転し、内部のViewも同様に反転させる。

struct ReversedListView: View {
    var numberOfItems: Int

    var body: some View {
        ScrollView {
            LazyVStack(alignment: .leading) {
                ForEach((0 ..< numberOfItems).reversed(), id: \.self) { index in
                    ColorCell(num: index)
                        .frame(height: 40)
                        .rotationEffect(.degrees(180))
                }
            }
        }
        .rotationEffect(.degrees(180))
    }
}

この方法の場合、iOS 17では方法2と同じ挙動、iOS 16以下では方法1と同じ挙動になる。
挙動がOSによって変わるのは、本番では少し使いづらいかもしれない。

サンプル↓

まとめ

結果をまとめると以下のようになる。

  1. リスト全体を反転する方法
    • 一番簡単
    • 新しい要素が追加されると全体が上に動く
  2. リストの上部にInsetを作る方法
    • 比較的簡単
    • 要素が追加されても全体が動かない
  3. UICollectionViewLayoutをカスタムする方法
    • 最も柔軟
    • 実装が複雑
  4. (番外編) SwiftUIで反転する方法
    • SwiftUIなのでリスト自体の実装が楽
    • OSバージョンによって挙動が異なる

多くの場合は方法1で問題ないため、基本的には簡単な方法1を使い、挙動が仕様に合わない場合は他の案を検討するのが良さそうだ。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?