github.com
紹介するコードは上記リポジトリにあげてあります。
はまりどころ
1. itemSize と groupSize を .estimated にする必要がある
例えば、height が可変の場合、グループの heightDimension を .estimated にして、レイアウトアイテムの heightDimension は .fractionalHeight にすれば良いかと思ったけど、それだとダメだった。
func makeLayoutSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let itemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let itemGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: itemGroupSize,
subitem: item,
count: 1
)
return .init(group: itemGroup)
}
2. グループを UICollectionView のスクロール方向と同じ方向で count を指定して作ると Self-Sizing されない
例えば、UICollectionView のスクロールが縦方向の場合、 グループを vertical で構築するとグループ直下のアイテムの height が Self-Sizing されず、 .estimated に指定した値がそのまま height に反映されてしまう。
func makeLayoutSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let itemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let itemGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: itemGroupSize,
subitem: item,
count: 1
)
let itemGroup = NSCollectionLayoutGroup.vertical(
layoutSize: itemGroupSize,
subitems: [item],
)
return .init(group: itemGroup)
}
3. レイアウトアイテムの Self-Sizing 方向の contentInsets が効かず、垂直方向の contentInsets が設定通りに反映されない
.absolute の時には問題なく反映される contentInsets が、 .estimated を利用している時には意図した通りに反映されない。
これは 公式ドキュメントにも書いてあった。
Note
The value of this property is ignored for any axis that uses an estimated value for its dimension.
ただ、Self-Sizing 方向と垂直方向の挙動は謎で、縦方向スクロールの UICollectionView でレイアウトアイテムの height を Self-Sizing している場合において、 leading に設定した値は leading, trailing の両方に反映され、 trailing に設定した値は2倍されて trailing に反映されてしまった。
func makeLayoutSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(
top: 16.0,
leading: 16.0,
bottom: 16.0,
trailing: 16.0
)
let itemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let itemGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: itemGroupSize,
subitem: item,
count: 1
)
return .init(group: itemGroup)
}
4. グループの Self-Sizing 方向の contentInsets が効かない
NSCollectionLayoutGroup は NSCollectionLayoutItem のサブクラスなので、はまりどころの3同様に Self-Sizing 方向の contentInsets は効かない。
ただ、 Self-Sizing と垂直方向の挙動は異なり、こちらは設定した通りの値がレイアウトに反映される。
func makeLayoutSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let itemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let itemGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: itemGroupSize,
subitem: item,
count: 1
)
itemGroup.contentInsets = .init(
top: 16.0,
leading: 16.0,
bottom: 16.0,
trailing: 16.0
)
return .init(group: itemGroup)
}
レイアウト例
1. 縦方向にスクロールする UICollectionView で同じレイアウトアイテムを縦に複数並べて Self-Sizing させる
レイアウト例1
func makeLayoutSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let itemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let itemGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: itemGroupSize,
subitem: item,
count: 1
)
itemGroup.contentInsets = .init(
top: .zero,
leading: 16.0,
bottom: .zero,
trailing: 16.0
)
let section = NSCollectionLayoutSection(group: itemGroup)
section.contentInsets = .init(
top: 16.0,
leading: .zero,
bottom: 16.0,
trailing: .zero
)
section.interGroupSpacing = 8.0
return section
}
注意点
例えば、縦に並べるレイアウトアイテム数が 3 つ固定の場合、下記のように group を vertical にして count を 3 とかにしたくなるけど、はまりどころの 2 で述べた通り Self-Sizing されなくなってしまう。
let itemGroup = NSCollectionLayoutGroup.vertical(
layoutSize: itemGroupSize,
subitem: item,
count: 3
)
let itemGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: itemGroupSize,
subitem: item,
count: 1
)
let itemGroup = NSCollectionLayoutGroup.vertical(
layoutSize: itemGroupSize,
subitems: [item, item, item],
)
2. 縦方向にスクロールする UICollectionView で異なるレイアウトアイテムを縦に複数並べて Self-Sizing させる
例えば、大きいレイアウトアイテムを3つ、小さいレイアウトアイテムを2つ表示する場合。
レイアウト例2
方法1
それぞれのレイアウトアイテム種類毎にグループを作って、さらにそれらをまとめるグループを作るやり方。
それぞれのグループに同じ interItemSpacing を設定しないといけないけど、反対にグループ毎に変えたい場合はこの方法が良い。
func makeLayoutSection() -> NSCollectionLayoutSection {
let interItemSpacing: NSCollectionLayoutSpacing = .fixed(8.0)
let largeItemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let largeItem = NSCollectionLayoutItem(layoutSize: largeItemSize)
let largeItemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(300.0)
)
let largeItemGroup = NSCollectionLayoutGroup.vertical(
layoutSize: largeItemGroupSize,
subitems: [largeItem, largeItem, largeItem]
)
largeItemGroup.contentInsets = .init(
top: .zero,
leading: 16.0,
bottom: .zero,
trailing: 16.0
)
largeItemGroup.interItemSpacing = interItemSpacing
let smallItemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let smallItem = NSCollectionLayoutItem(layoutSize: smallItemSize)
let smallItemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let smallItemGroup = NSCollectionLayoutGroup.vertical(
layoutSize: smallItemGroupSize,
subitems: [smallItem, smallItem]
)
smallItemGroup.contentInsets = .init(
top: .zero,
leading: 48.0,
bottom: .zero,
trailing: 48.0
)
smallItemGroup.interItemSpacing = interItemSpacing
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(500.0)
)
let group = NSCollectionLayoutGroup.vertical(
layoutSize: groupSize,
subitems: [
largeItemGroup,
smallItemGroup
]
)
group.interItemSpacing = interItemSpacing
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(
top: 16.0,
leading: .zero,
bottom: 16.0,
trailing: .zero
)
return section
}
方法2
全レイアウトアイテム毎にグループを作って、それらをまとめるグループを作るやり方。
レイアウトアイテム間のスペースの指定は、最上位のグループの interItemSpacing を指定するだけで良い。
func makeLayoutSection() -> NSCollectionLayoutSection {
let largeItemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let largeItem = NSCollectionLayoutItem(layoutSize: largeItemSize)
let largeItemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let largeItemGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: largeItemGroupSize,
subitem: largeItem,
count: 1
)
largeItemGroup.contentInsets = .init(
top: .zero,
leading: 16.0,
bottom: .zero,
trailing: 16.0
)
let smallItemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let smallItem = NSCollectionLayoutItem(layoutSize: smallItemSize)
let smallItemGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(100.0)
)
let smallItemGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: smallItemGroupSize,
subitem: smallItem,
count: 1
)
smallItemGroup.contentInsets = .init(
top: .zero,
leading: 48.0,
bottom: .zero,
trailing: 48.0
)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(500.0)
)
let group = NSCollectionLayoutGroup.vertical(
layoutSize: groupSize,
subitems: [
largeItemGroup, largeItemGroup, largeItemGroup,
smallItemGroup, smallItemGroup
]
)
group.interItemSpacing = .fixed(8.0)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(
top: 16.0,
leading: .zero,
bottom: 16.0,
trailing: .zero
)
return section
}
注意点
subitems: [
largeItemGroup, largeItemGroup, largeItemGroup,
smallItemGroup, smallItemGroup
]
largeItemGroup, smallItemGroup はそれぞれ一つのレイアウトアイテムしか保持していないので、グループを作らずに group の subitem に直接それぞれのレイアウトアイテム(largeItem, smallItem)を設定すれば良いかと思ったけど、はまりどころの3で述べたように、レイアウトアイテムの contentInsets は意図した通りに反映されないので、グループでラップしてそれらの contentInsets を設定している。
group.interItemSpacing = .fixed(8.0)
それぞれのアイテム間のスペースの設定をするための上記コードだけど、最初は、 グループ間のスペースを設定するのだからと思って下記のように section.interGroupSpacing に設定してしまったけど、それだと効かなかった。
section.interGroupSpacing = 8.0 🙅<200d>♀️
というのも、 largeItemGroup や smallItemGroup は確かにグループだけど、それらはネストしたグループであって、最上位にはそれらをまとめたグループがいて、 section.interGroupSpacing はその最上位のグループが繰り返される場合に最上位グループ間のスペースを確保するというものなのであった。
その他
NSCollectionLayoutGroup に visualDescription() なるものがあって試してみたけど、うーん?という感じだった。
Instance Method
visualDescription()
Returns a string with an ASCII representation of the group.
Apple Developer Documentation
NSCollectionLayoutItem に edgeSpacing というのがあった。
より複雑なレイアウト作るときには使えそう?
edgeSpacing
The amount of space added around the boundaries of the item between other items and this item's container.
Apple Developer Documentation
関連
komaji504.hateblo.jp