UICollectionViewFlowLayoutが予期せぬ配置してたのでちょっと深堀りしてみた
UICollectionView
って各Cellのサイズ調整が簡単にできて結構好きなのですが、
ちょっと「おぉ...こんな挙動するのか」っていうケースがあったのでそのメモ。
UICollectionView
...Cell配置のアルゴリズムよくわからん...
です(笑)
環境
- Xcode v9.2
やりたいこと
今回の話は、レイアウト次第な話な気がするので...
まずはどんなレイアウトにしようとしたかっていう説明から。
1件目のCellは画面幅いっぱいにして、ソレ以降は2カラムで配置していく...
っていうそこまでトリッキーじゃない、むしろ王道みたいなレイアウトです。
こういう感じです。
再現コード
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
private let items: [UIColor] = [
.blue,
.red,
.yellow,
.green
]
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "CELL")
self.collectionView.dataSource = self
self.collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CELL", for: indexPath)
cell.contentView.backgroundColor = self.items[indexPath.row]
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width
let half = width * 0.5
if indexPath.row == 0 {
return CGSize(width: width, height: half)
} else {
return CGSize(width: half, height: half)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return .zero
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return .leastNormalMagnitude
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return .leastNormalMagnitude
}
}
至って普通だと思います(たぶん)
これで、目指したいレイアウトは実現できているのですが...
とあるケースのときだけ、ちょっと「お?...おぅ」みたいな感じになります。
意図していないレイアウト
表示する件数を2件にします。
items を下記のように変更するだけ
private let items: [UIColor] = [
.blue,
.red
]
これでやってみると...
...まさかのセンタリング!
Cellの配置は、Sizeそのまま入るならminSpacingを考慮した上でそのまま配置、入り切らない場合は、spacingで埋めるように配置(UIToolbarなんかと近いイメージ)とか思っていて、
この場合だと赤色は左詰めになるかなぁ〜とかっておもっていたのだけれど。
spaceingが均等に割り当たるからかな...と考えていると
blueとgreenで違うし...
spacingがどういう感じで埋められるのかイマイチわからない...🤔
とりあえず...CellをTopLeftで配置したいならまかせっきりっじゃダメだということですね!
ちなみに、表示する件数を1つにして、常に2カラム表示とした場合はどうなるのかというと...
まぁ...こうですよね。。
Cell幅を width * 0.8 ぐらいで一律表示すると、全部センタリングになるのですけどね...
対策
UICollectionViewFlowLayout
のサブクラスを作って、
layoutAttributesForElements(in:)
とかlayoutAttributesForItem(at:)
とかをを override して自分で位置調整をするとちゃんとできます。
今回の例で対応するとしたら、それぞれでIndexPath
が参照できるので、
if indexPath.row == 0 || indexPath.row % 2 == 1 {
frame.x = 0
} else {
frame.x = size.width
}
こういう感じでしょうか(いろいろ端折ってますが)
まぁ・・・今回の実現したいレイアウトの場合だと、表示するアイテムが2件になったときだけ発現するものなので、目を瞑るっていうのも有りかと思います...(雑な 笑)