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

Posted

技术标签:

【中文标题】我如何实现这种布局,我只需要为偶数行号应用 Y 偏移量?【英文标题】:How do I achieve this layout, I just need to apply Y offset for even row numbers? 【发布时间】:2021-10-05 02:19:36 【问题描述】:

我已经提到了这个问题

Refer to this question and answer

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

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

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

这是那个答案中提到的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]
    

【问题讨论】:

“每当用户将新项目添加到集合视图单元格时。它会出现,但集合视图中已经存在的其他集合视图单元格消失了。” -- 首先,这令人困惑...用户是否添加了另一个 CELL?或者用户是否正在做一些向现有单元格添加一些东西(子视图?)的事情?您只展示了您的布局的代码——您没有展示任何与“添加”任何东西相关的代码。 【参考方案1】:

我了解您的问题是:

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

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

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
        

    ....

    

    ....


【讨论】:

伙计,你是救命稻草 :) 我已经为此头疼了好几天。知道它只需要一点修复就感觉很愚蠢:)非常感谢:)【参考方案2】:

我建议使用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, constant: padding),
            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

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

【讨论】:

非常感谢您的详细解释。我是应用程序开发的新手。你刚刚让学习变得更好。肯定会学习并应用。再次感谢您的帮助:) 意义重大 乐于助人。如果我的回答对你有用,请考虑投票:-)... 已经投票了,但是由于我是新人并且声誉低,它说它已记录但不会显示。 :-/

以上是关于我如何实现这种布局,我只需要为偶数行号应用 Y 偏移量?的主要内容,如果未能解决你的问题,请参考以下文章

iphone中的所有应用程序都以“网格图标”布局出现,我该如何实现这种布局?

JavaScript ES6函数式编程:柯里化偏应用组合管道

表格隔行变色以及鼠标移入高亮显示

Android联系人应用布局[关闭]

如何从文件中读取特定行(按行号)?

如何知道何时显示布局中的特定视图在RecyclerView中