具有动态缩放单元格的 UICollectionView

UICollectionView with dynamically-scaling cells

我的目标是创建如下所示的布局:

我知道如何创建这些自定义 UICollectionViewCells,但我在布局方面遇到了麻烦。所有显示的单元格的宽度都不同,因此可以有,例如:第一行有四个,第二行有两个,剩下的最后一个 - 在第三行。这只是一种可能的配置,根据标签的宽度,还有更多配置(包括图像上显示的配置)。

我正在以编程方式构建所有内容。我也觉得使用 UICollectionView 是最好的选择,但我愿意接受任何建议。

提前致谢!

我已经尝试过的:

let collectionView = UICollectionView(frame: .zero, collectionViewLayout: TagLayout()

override func viewDidLoad() {
    super.viewDidLoad()
    setupCollectionView()
}

private func setupCollectionView() {
    collectionView.backgroundColor = .systemGray5
    collectionView.dataSource = self
    collectionView.delegate = self
    collectionView.register(SubjectCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        
    //adding a view to subview and constraining it programmatically using Stevia
}

extension ProfileVC: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5
    }
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubjectCollectionViewCell else { return UICollectionViewCell() }
        cell.data = SubjectTagData(emoji: "", subjectName: "Item I")
        
        return cell
    }
}

使用以下 collectionViewLayout

    // MARK: - TagLayoutDelegate
    protocol TagLayoutDelegate: class {
        func widthForItem(at indexPath: IndexPath) -> CGFloat
        func rowHeight() -> CGFloat
    }

    // MARK: - TagLayout
    class TagLayout: UICollectionViewLayout {

    // MARK: Variables
    weak var delegate       : TagLayoutDelegate?
    var cellPadding         : CGFloat = 5.0
    var deafultRowHeight    : CGFloat = 35.0
    var scrollDirection     : UICollectionView.ScrollDirection = .vertical
    
    private var contentWidth: CGFloat = 0
    private var contentHeight: CGFloat = 0
    
    private var cache: [UICollectionViewLayoutAttributes] = []
    
    // MARK: Public Functions
    func reset() {
        cache.removeAll()
        contentHeight = 0
        contentWidth = 0
    }
    
    // MARK: Override
    override var collectionViewContentSize: CGSize {
        return CGSize(width: contentWidth, height: contentHeight)
    }
    
    override func prepare() {
        super.prepare()
        if scrollDirection == .vertical {
            prepareForVerticalScroll()
        }
        else {
            prepareForHorizontalScroll()
        }
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var visibleLayoutAttributeElements: [UICollectionViewLayoutAttributes] = []
        for attribute in cache {
            if attribute.frame.intersects(rect) {
                visibleLayoutAttributeElements.append(attribute)
            }
        }
        return visibleLayoutAttributeElements
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cache[indexPath.item]
    }
    
    // MARK: Private Functions
    private func prepareForVerticalScroll() {
        guard cache.isEmpty, let collectionView = collectionView else {
            return
        }
        
        let noOfItems   = collectionView.numberOfItems(inSection: 0)
        var xOffset     = [CGFloat](repeating: 0.0, count: noOfItems)
        var yOffset     = [CGFloat](repeating: 0.0, count: noOfItems)
        
        let insets      = collectionView.contentInset
        contentWidth = collectionView.bounds.width - (insets.left + insets.right)
        
        var rowWidth: CGFloat = 0
        for i in 0 ..< noOfItems {
            let indexPath = IndexPath(item: i, section: 0)
            let textWidth = delegate?.widthForItem(at: indexPath) ?? 75.0
            let width = textWidth + cellPadding
            let height = delegate?.rowHeight() ?? 30.0
            let frame = CGRect(
                x: xOffset[i],
                y: yOffset[i],
                width: width,
                height: height
            )
            let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
            
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = insetFrame
            cache.append(attributes)
            
            contentHeight = max(contentHeight, frame.maxY)
            
            
            rowWidth += frame.width
            
            if i < noOfItems-1 {
                let nextIP = IndexPath(item: i+1, section: 0)
                let nextWidth = delegate?.widthForItem(at: nextIP) ?? 75.0
                if rowWidth + nextWidth + cellPadding <= contentWidth {
                    xOffset[i+1] = xOffset[i] + width
                    yOffset[i+1] = yOffset[i]
                }
                else {
                    rowWidth = 0
                    yOffset[i+1] = yOffset[i] + (delegate?.rowHeight() ?? 30.0)
                }
            }
        }
    }
    
    private func prepareForHorizontalScroll() {
        guard cache.isEmpty, let collectionView = collectionView else {
            return
        }
        
        let insets = collectionView.contentInset
        contentHeight = collectionView.bounds.height - (insets.top + insets.bottom)
        
        let rowHeight = delegate?.rowHeight() ?? deafultRowHeight
        
        let noOfRows: Int = 2
        
        var yOffset: [CGFloat] = []
        for row in 0 ..< noOfRows {
            yOffset.append(CGFloat(row) * rowHeight)
        }
        
        var row = 0
        var xOffset: [CGFloat] = [CGFloat](repeating: 0.0, count: noOfRows)
        
        for i in 0 ..< collectionView.numberOfItems(inSection: 0) {
            let indexPath = IndexPath(item: i, section: 0)
            let textWidth = delegate?.widthForItem(at: indexPath) ?? 75.0
            let width = textWidth + cellPadding
            let frame = CGRect(
                x       : xOffset[row],
                y       : yOffset[row],
                width   : width,
                height  : rowHeight)
            let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
            
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = insetFrame
            cache.append(attributes)
            
            contentWidth = max(contentWidth, frame.maxX)
            xOffset[row] = xOffset[row] + width
            
            row = row < (noOfRows - 1) ? row + 1 : 0
        }
     }
   }

并实现如下

let tagLayout = TagLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: tagLayout)

private func setupCollectionView() {
  tagLayout.delegate = self
  //Your other code goes here
}

extension ProfileVC: TagLayoutDelegate {
  func widthForItem(at indexPath: IndexPath) -> CGFloat {
    return 100.0
  }
  func rowHeight() -> CGFloat {
    return 30.0
  }
}