iOSでセルが下から上に向かって表示されるリストの作成方法について検討してみた。実装例は以下のリポジトリにある↓
各方法の最後にも該当するコードへのリンクを記載している。
方法1 リスト全体を反転させる
一番簡単な方法。
UICollectionView
やUITableView
をtransform
で上下反転し、表示するセルも同様に反転させることで、下から上に向かって表示されるように見せる。
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と異なり要素全体が上がらない比較的簡単な方法。
UICollectionView
やUITableView
のcontentSize.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をカスタムする
一番面倒だが一番自由度が高い方法。
UICollectionViewFlowLayout
かUICollectionViewLayout
を継承したクラスを作り、自分でレイアウトを実装する。
Customizing Collection View Layouts
今回は簡単にUICollectionViewFlowLayout
を使って高さ固定を前提に作成した。
セル全ての高さcontentSize.height
が表示領域より小さい場合、表示領域を座標系にして下部詰めで各セルを並べるようにしている。
(本来prepare()
で諸々の計算をすべきだが、ここでは極力シンプルにするため省略している)
サンプルでは方法2と同じように下に要素が追加されても要素全体が上がらないように実装しているが、方法1と同じように作ることも可能なはずだ。
サンプル↓
番外編 方法1の方法を使ったSwiftUIでの実装方法
方法1のView全体を反転する方法はSwiftUIでも使える。
以下のようにScrollView
やList
などを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によって変わるのは、本番では少し使いづらいかもしれない。
サンプル↓
まとめ
結果をまとめると以下のようになる。
- リスト全体を反転する方法
- 一番簡単
- 新しい要素が追加されると全体が上に動く
- リストの上部にInsetを作る方法
- 比較的簡単
- 要素が追加されても全体が動かない
- UICollectionViewLayoutをカスタムする方法
- 最も柔軟
- 実装が複雑
- (番外編) SwiftUIで反転する方法
- SwiftUIなのでリスト自体の実装が楽
- OSバージョンによって挙動が異なる
多くの場合は方法1で問題ないため、基本的には簡単な方法1を使い、挙動が仕様に合わない場合は他の案を検討するのが良さそうだ。