使用 UICollectionViewCompositionalLayout 时,有没有办法只为数据源中的某些项目设置自定义大小?

问题描述 投票:0回答:1

假设您使用的是完全典型的布局:

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 { ...

解决方案可能类似于...

(A)...

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
的“超级”(而且我不知道这是否确实是组合布局的值)。

(B)...

let layoutSize = NSCollectionLayoutSize(
  widthDimension: .fractionalWidth(1/  item == 666 ? 1 : 3 ),

但我不知道如何在组合布局中使用indexPath(据我所知,您可以使用该部分,但不能使用索引路径)

(C)...

    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
}
swift uicollectionview uikit uicollectionviewcompositionallayout
1个回答
0
投票

这是使用

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()

现在你得到:

© www.soinside.com 2019 - 2024. All rights reserved.