CollectionView Flowlayout 自定义

Posted

技术标签:

【中文标题】CollectionView Flowlayout 自定义【英文标题】:CollectionView Flowlayout Customize 【发布时间】:2018-02-21 11:58:09 【问题描述】:

我正在制作个人资料图片 collectionview,例如 tinder 编辑个人资料图片。我想要第一个单元格比其他单元格大,除了第一个单元格之外还有 2、3 个单元格,其他单元格应该像 3、4、5。 有什么建议吗?

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
        if indexPath.item == 0 
        return CGSize(width: 213.34, height: 213.34)
         else 
            return CGSize(width: 101.66, height:101.66 )
        

    


    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return 6
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        let lbl = cell.viewWithTag(1) as! UILabel
        lbl.text = String(format: "%d", indexPath.row + 1)
        return cell
    



【问题讨论】:

在堆栈视图中使用堆栈视图 它也应该是可拖动的。 您的问题是您需要 2 个以下的 3 个? 你能提供你目前拥有的代码吗? youtube.com/watch?v=k47D67a1zJ8 【参考方案1】:

你需要实现一个UICollectionViewLayout,我称之为FillingLayout,注意你可以使用delegate方法调整大单元格的列数和大小

说明

您需要添加一个数组来跟踪您的列高度并查看最短的列是private var columsHeights : [CGFloat] = [],您还需要一个(Int,Float) 元组数组来保留哪些空间可以填充,我还添加了委托中的一个方法来获取我们想要在集合视图中的列数,以及一个方法来了解是否可以根据单元格的大小将单元格添加到某个位置。

然后如果我们要添加一个单元格我们检查是否可以添加,因为第一列已填满我们在avaiableSpaces数组中添加与column2对应的空间,当我们首先添加下一个单元格时我们检查是否可以添加到任何可用空间中,如果可以添加,我们添加和删除可用空间。

这里是完整的代码

import UIKit

protocol FillingLayoutDelegate: class 
    func collectionView(_ collectionView:UICollectionView, sizeForViewAtIndexPath indexPath:IndexPath) -> Int
    //  Returns the amount of columns that have to display at that moment
    func numberOfColumnsInCollectionView(collectionView:UICollectionView) ->Int


class FillingLayout: UICollectionViewLayout 
    weak var delegate: FillingLayoutDelegate!

    fileprivate var cellPadding: CGFloat = 10

    fileprivate var cache = [UICollectionViewLayoutAttributes]()

    fileprivate var contentHeight: CGFloat = 0
    private var columsHeights : [CGFloat] = []
    private var avaiableSpaces : [(Int,CGFloat)] = []

    fileprivate var contentWidth: CGFloat 
        guard let collectionView = collectionView else 
            return 0
        
        let insets = collectionView.contentInset
        return collectionView.bounds.width - (insets.left + insets.right)
    

    var columnsQuantity : Int
        get
            if(self.delegate != nil)
            
                return (self.delegate?.numberOfColumnsInCollectionView(collectionView: self.collectionView!))!
            
            return 0
        
    

    //MARK: PRIVATE METHODS
    private func shortestColumnIndex() -> Int
        var retVal : Int = 0
        var shortestValue = MAXFLOAT

        var i = 0
        for columnHeight in columsHeights 
            //debugPrint("Column Height: \(columnHeight) index: \(i)")
            if(Float(columnHeight) < shortestValue)
            
                shortestValue = Float(columnHeight)
                retVal = i
            
            i += 1
        
        //debugPrint("shortest Column index: \(retVal)")
        return retVal
    

    //MARK: PRIVATE METHODS
    private func largestColumnIndex() -> Int
        var retVal : Int = 0
        var largestValue : Float = 0.0

        var i = 0
        for columnHeight in columsHeights 
            //debugPrint("Column Height: \(columnHeight) index: \(i)")
            if(Float(columnHeight) > largestValue)
            
                largestValue = Float(columnHeight)
                retVal = i
            
            i += 1
        
        //debugPrint("shortest Column index: \(retVal)")
        return retVal
    

    private func canUseBigColumnOnIndex(columnIndex:Int,size:Int) ->Bool
    
        if(columnIndex < self.columnsQuantity - (size-1))
        
            let firstColumnHeight = columsHeights[columnIndex]
            for i in columnIndex..<columnIndex + size
                if(firstColumnHeight != columsHeights[i]) 
                    return false
                
            
            return true
        

        return false
    

    override var collectionViewContentSize: CGSize 
        return CGSize(width: contentWidth, height: contentHeight)
    

    override func prepare() 
        // Check if cache is empty
        guard cache.isEmpty == true, let collectionView = collectionView else 
            return
        

        //  Set all column heights to 0
        self.columsHeights = []
        for _ in 0..<self.columnsQuantity 
            self.columsHeights.append(0)
        

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

            let indexPath = IndexPath(item: item, section: 0)

            let viewSize: Int = delegate.collectionView(collectionView, sizeForViewAtIndexPath: indexPath)
            let blockWidth = (contentWidth/CGFloat(columnsQuantity))
            let width = blockWidth * CGFloat(viewSize)
            let height = width

            var columIndex = self.shortestColumnIndex()
            var xOffset = (contentWidth/CGFloat(columnsQuantity)) * CGFloat(columIndex)
            var yOffset = self.columsHeights[columIndex]

            if(viewSize > 1)//Big Cell
                if(!self.canUseBigColumnOnIndex(columnIndex: columIndex,size: viewSize))
                    //  Set column height
                    for i in columIndex..<columIndex + viewSize
                        if(i < columnsQuantity)
                            self.avaiableSpaces.append((i,yOffset))
                            self.columsHeights[i] += blockWidth
                        
                    
                    //  Set column height
                    yOffset = columsHeights[largestColumnIndex()]
                    xOffset = 0
                    columIndex = 0
                

                for i in columIndex..<columIndex + viewSize
                    if(i < columnsQuantity)
                        //current height
                        let currValue = self.columsHeights[i]
                        //new column height with the update
                        let newValue = yOffset + height
                        //space that will remaing in blank, this must be 0 if its ok
                        let remainder = (newValue - currValue) - CGFloat(viewSize) * blockWidth
                        if(remainder > 0) 
                            debugPrint("Its bigger remainder is \(remainder)")
                            //number of spaces to fill
                            let spacesTofillInColumn = Int(remainder/blockWidth)
                            //we need to add those spaces as avaiableSpaces
                            for j in 0..<spacesTofillInColumn 
                                self.avaiableSpaces.append((i,currValue + (CGFloat(j)*blockWidth)))
                            
                        
                        self.columsHeights[i] = yOffset + height
                    
                
            else
                //if there is not avaiable space
                if(self.avaiableSpaces.count == 0)
                
                    //  Set column height
                    self.columsHeights[columIndex] += height
                else//if there is some avaiable space
                    yOffset = self.avaiableSpaces.first!.1
                    xOffset = CGFloat(self.avaiableSpaces.first!.0) * width
                    self.avaiableSpaces.remove(at: 0)
                
            

            print(width)

            let frame = CGRect(x: xOffset, y: yOffset, 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)
        
    

    func getNextCellSize(currentCell: Int, collectionView: UICollectionView) -> Int 
        var nextViewSize = 0
        if currentCell < (collectionView.numberOfItems(inSection: 0) - 1) 
            nextViewSize = delegate.collectionView(collectionView, sizeForViewAtIndexPath: IndexPath(item: currentCell + 1, section: 0))
        
        return nextViewSize
    

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

        var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()

        // Loop through the cache and look for items in the rect
        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]
    

更新

您需要将 viewController 设置为 FillingLayoutDelegate

override func viewDidLoad() 
    super.viewDidLoad()

    collectionView.delegate = self
    collectionView.dataSource = self

    // Do any additional setup after loading the view.
    if let layout = self.collectionView.collectionViewLayout as? FillingLayout
    
        layout.delegate = self
    


您的 ViewController 中的 FillingLayoutDelegate 实现

extension ViewController: FillingLayoutDelegate
func collectionView(_ collectionView:UICollectionView,sizeForViewAtIndexPath indexPath:IndexPath) ->Int
        if(indexPath.row == 0 || indexPath.row == 4)
        
            return 2
        

        if(indexPath.row == 5)
        
            return 3
        

        return 1
    

    func numberOfColumnsInCollectionView(collectionView:UICollectionView) ->Int
        return 3
    

屏幕截图工作

【讨论】:

【参考方案2】:

您可以使用UICollectionViewLayout 来处理此问题。代码如下:

UICollectionViewLayout 类来定义布局:

class CustomCircularCollectionViewLayout: UICollectionViewLayout 

var itemSize = CGSize(width: 200, height: 150)
var attributesList = [UICollectionViewLayoutAttributes]()

override func prepare() 
    super.prepare()

    let itemNo = collectionView?.numberOfItems(inSection: 0) ?? 0
    let length = (collectionView!.frame.width - 40)/3
    itemSize = CGSize(width: length, height: length)

    attributesList = (0..<itemNo).map  (i) -> UICollectionViewLayoutAttributes in
        let attributes = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: i, section: 0))

        attributes.size = self.itemSize

        var x = CGFloat(i%3)*(itemSize.width+10) + 10
        var y = CGFloat(i/3)*(itemSize.width+10) + 10

        if i > 2 
            y += (itemSize.width+10)
            attributes.frame = CGRect(x: x, y: y, width: itemSize.width, height: itemSize.height)
         else if i == 0 
            attributes.frame = CGRect(x: x, y: y, width: itemSize.width*2+10, height: itemSize.height*2+10)
         else 
            x = itemSize.width*2 + 30
            if i == 2 
                y += itemSize.height + 10
            
            attributes.frame = CGRect(x: x, y: y, width: itemSize.width, height: itemSize.height)
        

        return attributes
    


override var collectionViewContentSize : CGSize 

    return CGSize(width: collectionView!.bounds.width, height: (itemSize.height + 10)*CGFloat(ceil(Double(collectionView!.numberOfItems(inSection: 0))/3))+(itemSize.height + 20))



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

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? 
    if indexPath.row < attributesList.count
    
        return attributesList[indexPath.row]
    
    return nil

override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool 
    return true

现在将这个类设置为你的集合视图布局:

self.collectionView!.collectionViewLayout = circularLayoutObject

这将显示您的网格,如下所示:

让我知道它是否适合你。

【讨论】:

以上是关于CollectionView Flowlayout 自定义的主要内容,如果未能解决你的问题,请参考以下文章

具有动态高度的水平 CollectionView 导致 FlowLayout 警告

flowLayout.itemSize 和 flowLayout.minimumInteritemSpacing

CollectionView 简用

如何从collectionview的数据源中获取项目数

collectionView itemW宽度计算不对

Collectionview 的底部约束未在运行时更新