繁体   English   中英

如何使水平 UICollectionView 在动态单元格之间具有相同的间距

[英]How to make a horizontal UICollectionView have the same spacing between dynamic cells

我有一个动态collectionView,基本上单元格之间的间距需要相同,无论单元格的宽度如何。

在这里和互联网上找到了类似的答案,但都是针对垂直滚动的 collectionViews。 因此,我继续尝试进一步研究其中一个答案以实现我想要的,但运气不佳。

目前,我的 collectionView 在单元格之间具有相同的间距,但是在每个单元格之后,它会移动到下一行,尽管我没有更改或操作属性的 y 偏移量。 此外,并非所有单元格都是可见的。

拜托,你能指出我做错了什么吗? 谢谢。

我正在使用的 UICollectionViewFlowLayout 的子类是:

class TagsLayout: UICollectionViewFlowLayout {
    
    let cellSpacing: CGFloat = 20
        override init(){
            super.init()
            scrollDirection = .horizontal
        }

        required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)!
            self.scrollDirection = .horizontal
        }

        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            guard let attributes = super.layoutAttributesForElements(in: rect) else {
                return nil
            }
            
            guard let attributesToReturn =  attributes.map( { $0.copy() }) as? [UICollectionViewLayoutAttributes] else {
                return nil
            }
            var leftMargin = sectionInset.left
            var maxX: CGFloat = -1.0
            attributesToReturn.forEach { layoutAttribute in
                if layoutAttribute.frame.origin.x >= maxX {
                    leftMargin = sectionInset.left
                }

                layoutAttribute.frame.origin.x = leftMargin

                leftMargin += layoutAttribute.frame.width + cellSpacing
                maxX = max(layoutAttribute.frame.maxX , maxX)
            }

            return attributesToReturn
        }
}

在此处输入图像描述

正如我在评论中所说,您正在使用“左对齐垂直滚动”集合视图的代码。

一个水平滚动的集合视图像这样布置单元格:

在此处输入图像描述

您的代码正在按顺序为每个单元格计算一个新的origin.x ,结果如下:

在此处输入图像描述

您可以修改自定义流布局以跟踪每个“行”的maxX ...但是,如果您在滚动时有很多单元格,那么前几个“列”就看不见了,那些单元格将不再被考虑到布局中。

因此,您可以尝试“预先计算”所有单元格的帧宽度和 x 原点,并接近您的目标:

在此处输入图像描述

不过还有两个问题...

首先,假设您的单元格包含比这些图像中显示的更长的字符串,则集合视图无法很好地确定哪些单元格实际上需要显示。 也就是说,集合视图将使用估计的项目大小来决定是否需要渲染单元格。 如果对单元格origin.x值的修改不在预期范围内,则某些单元格将不会被渲染,因为集合视图不会要求它们。

其次,如果你有不同宽度的标签,你可能会得到这样的结果:

在此处输入图像描述

并旋转到横向以强调(第一行实际上一直到 24):

在此处输入图像描述

您可能需要重新考虑您的方法,要么使用垂直滚动左对齐的集合视图,要么使用具有等宽单元格的水平滚动集合视图,或者其他一些方法(例如放置子视图的普通滚动视图-通过您自己的代码输出)。

我确实使用“预计算”方法创建了类——如果你想尝试一下,它们就在这里。

带有标签的简单单元格:

class TagCell: UICollectionViewCell {
    let label = UILabel()
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(label)
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0),
            label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
            label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
            label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0),
        ])
        
        // default (unselected) appearance
        contentView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        label.textColor = .black
        
        // let's round the corners so it looks nice
        contentView.layer.cornerRadius = 12
    }
}

修改后的自定义流布局:

class TagsLayout: UICollectionViewFlowLayout {
    
    var cachedFrames: [[CGRect]] = []
    
    var numRows: Int = 3
    
    let cellSpacing: CGFloat = 20

    override init(){
        super.init()
        commonInit()
    }
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        commonInit()
    }
    func commonInit() {
        scrollDirection = .horizontal
    }
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
//      guard let attributes = super.layoutAttributesForElements(in: rect) else {
//          return nil
//      }

        // we want to force the collection view to ask for the attributes for ALL the cells
        //  instead of the cells in the rect
        var r: CGRect = rect
        // we could probably get and use the max-width from the cachedFrames array...
        //  but let's just set it to a very large value for now
        r.size.width = 50000
        guard let attributes = super.layoutAttributesForElements(in: r) else {
            return nil
        }

        guard let attributesToReturn =  attributes.map( { $0.copy() }) as? [UICollectionViewLayoutAttributes] else {
            return nil
        }

        attributesToReturn.forEach { layoutAttribute in

            let thisRow: Int = layoutAttribute.indexPath.item % numRows
            let thisCol: Int = layoutAttribute.indexPath.item / numRows

            layoutAttribute.frame.origin.x = cachedFrames[thisRow][thisCol].origin.x
        }
        
        return attributesToReturn
    }
}

带有生成的标签字符串的示例控制器类:

class HorizontalTagColViewVC: UIViewController {
    
    var collectionView: UICollectionView!
    
    var myData: [String] = []
    
    // number of cells that will fit vertically in the collection view
    let numRows: Int = 3
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // let's generate some rows of "tags"
        //  we're using 3 rows for this example
        for i in 0...28 {
            switch i % numRows {
            case 0:
                // top row will have long tag strings
                myData.append("A long tag name \(i)")
            case 1:
                // 2nd row will have short tag strings
                myData.append("Tag \(i)")
            default:
                // 3nd row will have numeric strings
                myData.append("\(i)")
            }
        }
        
        // now we'll pre-calculate the tag-cell widths
        let szCell = TagCell()
        let fitSize = CGSize(width: 1000, height: 50)
        var calcedFrames: [[CGRect]] = Array(repeating: [], count: numRows)
        for i in 0..<myData.count {
            szCell.label.text = myData[i]
            let sz = szCell.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .defaultLow, verticalFittingPriority: .required)
            let r = CGRect(origin: .zero, size: sz)
            calcedFrames[i % numRows].append(r)
        }
        // loop through each "row" setting the origin.x to the
        //  previous cell's origin.x + width + 20
        for row in 0..<numRows {
            for col in 1..<calcedFrames[row].count {
                var thisRect = calcedFrames[row][col]
                let prevRect = calcedFrames[row][col - 1]
                thisRect.origin.x += prevRect.maxX + 20.0
                calcedFrames[row][col] = thisRect
            }
        }

        let fl = TagsLayout()
        // for horizontal flow, this is becomes the minimum-inter-line spacing
        fl.minimumInteritemSpacing = 20
        // we need this so the last cell does not get clipped
        fl.minimumLineSpacing = 20
        // a reasonalbe estimated size
        fl.estimatedItemSize = CGSize(width: 120, height: 50)
        
        // set the number of rows in our custom layout
        fl.numRows = numRows
        // set our calculated frames in our custom layout
        fl.cachedFrames = calcedFrames
        
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
        
        // so we can see the collection view frame
        collectionView.backgroundColor = .cyan
        
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(collectionView)
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            collectionView.heightAnchor.constraint(equalToConstant: 180.0),
        ])
        
        collectionView.register(TagCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self
        collectionView.delegate = self
        
    }
    
}
extension HorizontalTagColViewVC: UICollectionViewDataSource, UICollectionViewDelegate {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return myData.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! TagCell
        c.label.text = myData[indexPath.item]
        return c
    }
}

请注意,这只是示例代码!!! 它尚未经过测试,可能适合也可能不适合您的需求。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM