假设您使用的是完全典型的布局:
let layout = UICollectionViewCompositionalLayout { (_, _) -> NSCollectionLayoutSection? in
let layoutSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/CGFloat(3)),
heightDimension: .absolute(rowHeight)
)
let item = NSCollectionLayoutItem(layoutSize: layoutSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(rowHeight)
)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
return NSCollectionLayoutSection(group: group)
}
这是一个 3† 列布局:
但是,有时该部分(并且只有一个部分)中有一个项目是全宽(或可能是任何特定尺寸):
if daData[indexPath.item].whatever = .special { ...
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
if .. return .. the default from the compositional layout
else .. return some specific size
但我相信这是不可能的/我不知道如何返回
#sizeForItemAt
的“超级”(而且我不知道这是否确实是组合布局的值)。
let layoutSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/ item == 666 ? 1 : 3 ),
但我不知道如何在组合布局中使用indexPath(据我所知,您可以使用该部分,但不能使用索引路径)
widthDimension: estimated: .fractionalWidth(1/3),
...
groupSize .. similar idea
但我不知道如何将宽度设置为估计宽度和分数。
也许与
#sizeForItemAt
结合,但我不知道如何将列号组合布局与估计大小与 #sizeForItemAt
结合起来
有办法吗?
简而言之,理想的是:
脚注
† 在实践中,您必须处理旋转(或其他动画形状)。你可以在构图布局中非常优雅地做到这一点......
// let columnCount = self.bounds.width < self.bounds.height ? CGFloat(2) : CGFloat(3)
// more specifically, often best to use the whole screen ... in case of for example a slide-up panel
var columnCount = CGFloat(2)
if let glass = self.findViewController()?.view.bounds {
if glass.width > glass.height {
columnCount = CGFloat(3)
}
}
let layoutSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/columnCount), .. etc
记得
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collection.collectionViewLayout.invalidateLayout() // for rotation changes
}
这是使用
UICollectionViewFlowLayout
的相当简单的方法。此示例允许您以固定数量的列显示单元格,但允许某些项目全宽显示。
使用流布局在固定列中显示单元格的技巧是计算适当的项目大小。计算的宽度需要考虑集合视图的宽度、安全区域插入、部分插入以及行中单元格之间的迭代项间距。
代码中的注释提供了更多详细信息。
// Dummy data structure
struct ItemData {
let isFullSize: Bool
}
class ViewController: UICollectionViewController {
let layout: UICollectionViewFlowLayout = .init()
var data = [ItemData]()
let columns = 3
let rowHeight: CGFloat = 75
var columnWidth: CGFloat = 0.0
var fullWidth: CGFloat = 0.0
init() {
// Setup the layout as desired
layout.scrollDirection = .vertical
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
layout.sectionInset = .init(top: 0, left: 10, bottom: 0, right: 10)
layout.sectionInsetReference = .fromSafeArea
super.init(collectionViewLayout: layout)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
// Generate data where one specific item is shown full size
data = Array(repeating: ItemData(isFullSize: false), count: 18)
data[5] = ItemData(isFullSize: true)
}
private func updateSizes(for size: CGSize) {
// Account for the view's safeAreaInsets and the layout's sectionInsets
let cvWidth: CGFloat = size.width - view.safeAreaInsets.left - view.safeAreaInsets.right - layout.sectionInset.left - layout.sectionInset.right
fullWidth = cvWidth.rounded(.towardZero)
// Account for the layout's minimumInteritemSpacing
columnWidth = ((fullWidth - layout.minimumInteritemSpacing * CGFloat(columns - 1)) / CGFloat(columns)).rounded(.towardZero)
self.collectionView.collectionViewLayout.invalidateLayout()
}
// Adjust for updates to the safe area
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
updateSizes(for: view.bounds.size)
}
// Adjust for initial display
override func viewIsAppearing(_ animated: Bool) {
super.viewIsAppearing(animated)
updateSizes(for: view.bounds.size)
}
// Adjust for device rotation or iPad split screen
override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
updateSizes(for: size)
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Show a trivial green square for each cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .green
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Set the item's width based on whether it's full size or not
let item = data[indexPath.row]
if item.isFullSize {
return CGSize(width: fullWidth, height: rowHeight)
} else {
return CGSize(width: columnWidth, height: rowHeight)
}
}
}
正如您所看到的,这种流布局的使用存在一个有争议的“缺陷”——只有 1 或 2 个单元格的行不会使这些单元格左对齐。这是通过子类化
UICollectionViewFlowLayout
并使用子类来解决的。
请参阅UICollectionView 中的左对齐单元格以获取许多解决方案。以下示例基于此答案:
class LeftAlignedFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let att = super.layoutAttributesForElements(in:rect) else {return []}
var x: CGFloat = sectionInset.left
var y: CGFloat = -1.0
for a in att {
if a.representedElementCategory != .cell { continue }
if a.frame.origin.y >= y { x = sectionInset.left }
a.frame.origin.x = x
x += a.frame.width + minimumInteritemSpacing
y = a.frame.maxY
}
return att
}
}
改变:
let layout: UICollectionViewFlowLayout = .init()
至:
let layout: LeftAlignedFlowLayout = .init()
现在你得到: