我如何实现这种布局,我只需要为偶数行号应用 Y 偏移量?

我已经提到了这个问题

我如何实现这种布局,我只需要为偶数行号应用 Y 偏移量?

Refer to this question and answer

和提供的解决方案,当我实施它时,我观察到一个奇怪的行为。每当用户向集合视图单元格添加新项目时。它出现了,但集合视图中已经存在的其他集合视图单元格消失了。

只有在重新启动应用程序和视图后才会调用加载方法。我能够看到所有的集合视图项目。如果我尝试在不重新启动应用程序的情况下添加两个项目,它甚至会崩溃。

它在集合视图流布局中因超出范围错误而崩溃。

这是那个答案中提到的collectionviewflowlayout



`import UIKit

class HiveLayout: UICollectionViewLayout {

    // Adjust this as you need
    fileprivate var offset: CGFloat = 30
    

    //    Properties for configuring the layout: the number of columns and the cell padding.
    fileprivate var numberOfColumns = 2
    fileprivate var cellPadding: CGFloat = 10

    //    Cache the calculated attributes. When you call prepare(),you’ll calculate the attributes for all items and add them to the cache. You can be efficient and query the cache instead of recalculating them every time.
    fileprivate var cache = [UICollectionViewLayoutAttributes]()

    //    Properties to store the content size.
    fileprivate var contentHeight: CGFloat = 0
    fileprivate var contentWidth: CGFloat {
        guard let collectionView = collectionView else {
            return 0
        }
        let insets = collectionView.contentInset
        return collectionView.bounds.width - (insets.left + insets.right)
    }

    //    Using contentWidth and contentHeight from previous steps,calculate collectionViewContentSize.
    override var collectionViewContentSize: CGSize {
        return CGSize(width: contentWidth,height: contentHeight)
    }

    override func prepare() {
        // If cache is empty and the collection view exists – calculate the layout attributes
        guard cache.isEmpty == true,let collectionView = collectionView else {
            return
        }

        // xOffset: array with the x-coordinate for every column based on the column widths
        // yOffset: array with the y-position for every column,Using odd-even logic to push the even cell upwards and odd cells down.
        let columnWidth = contentWidth / CGFloat(numberOfColumns)
        var xOffset = [CGFloat]()
        for column in 0 ..< numberOfColumns {
            xOffset.append(CGFloat(column) * columnWidth)
        }
        var column = 0

        var yOffset = [CGFloat]()
        for i in 0..<numberOfColumns {
            yOffset.append((i % 2 == 0) ? 0 : offset)
        }

        for item in 0 ..< collectionView.numberOfItems(inSection: 0) {

            let indexPath = IndexPath(item: item,section: 0)
            
            let columnWidths = contentWidth / CGFloat(numberOfColumns)
            

            // Calculate insetframe that can be set to the attribute
//            let cellHeight = columnWidth - (cellPadding * 2)
//            let height = 250.0
            let height = columnWidths * 1.5
            let frame = CGRect(x: xOffset[column],y: yOffset[column],width: columnWidth,height: CGFloat(height))
            let insetframe = frame.insetBy(dx: cellPadding,dy: cellPadding)

            // Create an instance of UICollectionViewLayoutAttribute,sets its frame using insetframe and appends the attributes to cache.
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = insetframe
            cache.append(attributes)

            // Update the contentHeight to account for the frame of the newly calculated item. It then advances the yOffset for the current column based on the frame
            contentHeight = max(contentHeight,frame.maxY)
            yOffset[column] = yOffset[column] + CGFloat(height)

            column = column < (numberOfColumns - 1) ? (column + 1) : 0
        }
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

        var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()

        for attributes in cache {
            if attributes.frame.intersects(rect) {
                visibleLayoutAttributes.append(attributes)
            }
        }
        return visibleLayoutAttributes
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cache[indexPath.item]
    }
}
hacldt 回答:我如何实现这种布局,我只需要为偶数行号应用 Y 偏移量?

我了解您的问题是:

  • 如果您添加新数据并重新加载集合视图,则它不会在视图中显示新数据
  • 如果您将新数据插入列表数据并重新加载集合视图,它将显示新数据,同时一些旧数据也会消失
  • 集合视图只有在您按照您所说的重新启动应用程序时才能显示

我已经尝试过这个问题并像这样修复它,您只需要删除条件以检查缓存是否为空,然后集合视图可以重新加载新数据。

import UIKit
class HiveLayout: UICollectionViewLayout {
    
    ....
    
    override func prepare() {
        // If cache is empty and the collection view exists – calculate the layout attributes
        // Here I remove this codition: cache.isEmpty == true
        guard let collectionView = collectionView else {
            return
        }

    ....

    }

    ....

}
,

我建议使用 UICollectionViewCompositionalLayout 来实现这种布局。一旦你有了这个想法,它就会比 UICollectionViewFlowLayout 简单得多(至少在我看来)。
请注意,我使用了 UICollectionViewDataSource 来保持简单。您当然可以改用 UICollectionViewDiffableDataSource

视图控制器

class ViewController: UIViewController {
    private var items: [UIColor] = (1...16).map { _ in .systemTeal }
    
    private let collectionView = UICollectionView(frame: .zero,collectionViewLayout: .createCompositionalLayout())
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCollectionView()
    }
    
    private func setupCollectionView() {
        collectionView.dataSource = self
        collectionView.register(MyCell.self,forCellWithReuseIdentifier: MyCell.reuseID)
        collectionView.backgroundColor = .systemBackground
        
        view.addSubview(collectionView)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        
        let padding: CGFloat = 8
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.topAnchor,constant: padding),collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor,collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant: -padding),collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: -padding)
        ])
    }
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView,numberOfItemsInSection section: Int) -> Int {
        return items.count
    }
    
    func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCell.reuseID,for: indexPath) as? MyCell
        
        let item = items[indexPath.row]
        cell?.set(bgColor: item,text: "\(indexPath.row)")
        
        return cell ?? UICollectionViewCell()
    }
}

UICollectionViewLayout 扩展:

extension UICollectionViewLayout {
    static func createCompositionalLayout() -> UICollectionViewCompositionalLayout{
        return UICollectionViewCompositionalLayout { sectionIndex,layoutEnvironment in
            //Item
            let elementSize = NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(1),heightDimension: .fractionalHeight(0.3)
            )
            
            let leftItem = NSCollectionLayoutItem(layoutSize: elementSize)
            
            let rightItem = NSCollectionLayoutItem(layoutSize: elementSize)
            rightItem.contentInsets = NSDirectionalEdgeInsets(top: 40,leading: 0,bottom: -40,trailing: 0)
            
            //Vertical Groups
            let verticalGroupSize = NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(0.48),heightDimension: .fractionalHeight(1)
            )
            
            let leftVerticalGroup = NSCollectionLayoutGroup.vertical(layoutSize: verticalGroupSize,subitem: leftItem,count: 1)
            leftVerticalGroup.interItemSpacing = .fixed(10)

            let rightVerticalGroup = NSCollectionLayoutGroup.vertical(layoutSize: verticalGroupSize,subitem: rightItem,count: 1)
            rightVerticalGroup.interItemSpacing = .fixed(10)
            
            //Horizontal Group
            let groupSize = NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(1),heightDimension: .fractionalHeight(1/4)
            )
            
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,subitems: [leftVerticalGroup,rightVerticalGroup])
            group.interItemSpacing = .flexible(5)
            
            //Section
            let section = NSCollectionLayoutSection(group: group)
            section.interGroupSpacing = 10
            return section
        }
    }
}

集合视图单元格:

class MyCell: UICollectionViewCell {
    static var reuseID: String { String(describing: self) }
    
    private let label: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textAlignment = .center
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: .zero)
        contentView.layer.cornerRadius = 8
        setupLabel()
    }
    
    private func setupLabel() {
        contentView.addSubview(label)
        
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: contentView.topAnchor),label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func set(bgColor: UIColor,text: String) {
        contentView.backgroundColor = bgColor
        label.text = text
    }
}

只需将提供的代码添加到您的项目中,您应该能够看到以下结果(在新的空 Storyboard 应用程序中,只需删除默认的 ViewController

Expected Result

现在,如果您将一些项目添加到 items 并调用 collectionView.reloadData(),应该不会有任何问题。
查看此链接以开始使用 UICollectionViewCompositionalLayouthttps://jayeshkawli.ghost.io/new-collection-view-apis-composite-layout/

本文链接:https://www.f2er.com/16355.html

大家都在问