ios - UICollectionViewLayout 小型和大型单元自动布局

标签 ios swift uicollectionview swift4 uicollectionviewlayout

我有两个单元格尺寸小(间距和插入前大约一半屏幕宽度)和大(插入前全屏宽度),这两种尺寸可以在下图中看到。

Problem

我希望 UICollectionView 根据给定的大小属性自动调整这些单元格的大小。我已经设法正确调整单元格的大小,但是当没有两个小单元格按顺序排列时,我无法让单元格正确布局。

这是当两个小单元在阵列中按顺序排列时发生的情况:

Working

这是这个单元格应该去的地方:

enter image description here

这是我制作的自定义 UICollectionViewLayout 的准备函数:

import UIKit

protocol FlexLayoutDelegate: class {
    func collectionView(_ collectionView:UICollectionView, sizeForViewAtIndexPath indexPath:IndexPath) -> Int
}

class FlexLayout: UICollectionViewLayout {
    weak var delegate: FlexLayoutDelegate!

fileprivate var cellPadding: CGFloat = 10

fileprivate var cache = [UICollectionViewLayoutAttributes]()

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)
}

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
    }

    var yOffset = CGFloat(0)
    var xOffset = CGFloat(0)

    var column = 0

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

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

        let viewSize: CGFloat = CGFloat(delegate.collectionView(collectionView, sizeForViewAtIndexPath: indexPath))

        let height = cellPadding * 2 + 240

        let width = contentWidth / viewSize
        if viewSize == 2 {
            xOffset = CGFloat(column) * width
        } else {
            xOffset = 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)
        column = column < 1 ? column + 1 : 0
        yOffset = column == 0 || getNextCellSize(currentCell: indexPath.row, collectionView: collectionView) == 1 ? yOffset + height : yOffset


    }
}

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]
}

谢谢

最佳答案

您需要添加一个数组来跟踪您的列高度并查看最短的列是什么 private var columsHeights : [CGFloat] = []您还需要一个 数组(Int,Float) 元组来保留哪些空间可以被填充,我还在委托(delegate)中添加了一个方法来获取 Collection View 中我们想要的列数,以及一个方法来知道一个单元格是否可以被填充根据他们的大小添加或不在一个位置。

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

这是代码

FlexLayoutDelegate Implementation

extension ViewController: FlexLayoutDelegate{
    func collectionView(_ collectionView:UICollectionView,sizeForViewAtIndexPath indexPath:IndexPath) ->Int{
        if(indexPath.row % 2 == 1)
        {
            return 1
        }
        return 2
    }

    func numberOfColumnsInCollectionView(collectionView:UICollectionView) ->Int{
        return 2
    }
}

FlexLayout

import UIKit

protocol FlexLayoutDelegate: 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 FlexLayout: UICollectionViewLayout {
    weak var delegate: FlexLayoutDelegate!

    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
    }

    private func canUseDoubleColumnOnIndex(columnIndex:Int) ->Bool
    {
        var retVal = false
        if(columnIndex < self.columnsQuantity-1)
        {
            let firstColumnHeight = columsHeights[columnIndex]
            let secondColumnHeight = columsHeights[columnIndex + 1]

            //debugPrint(firstColumnHeight - secondColumnHeight)
            retVal = firstColumnHeight == secondColumnHeight
        }

        return retVal
    }

    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)
        }

        var xOffset = CGFloat(0)

        var column = 0

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

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

            let viewSize: CGFloat = CGFloat(delegate.collectionView(collectionView, sizeForViewAtIndexPath: indexPath))

            let height = cellPadding * 2 + 240

            let width = contentWidth / viewSize
            if viewSize == 2 {
                xOffset = CGFloat(column) * width
            } else {
                xOffset = 0
            }

            var columIndex = self.shortestColumnIndex()
            var yOffset = self.columsHeights[columIndex]

            if(viewSize == 1){//Double Cell
                if(self.canUseDoubleColumnOnIndex(columnIndex: columIndex)){
                    //  Set column height
                    self.columsHeights[columIndex] = CGFloat(yOffset) + height
                    self.columsHeights[columIndex + 1] = CGFloat(yOffset) + height
                }else{
                    self.avaiableSpaces.append((columIndex,yOffset))
                    //  Set column height
                    yOffset += height
                    xOffset = 0
                    columIndex = 0
                    self.columsHeights[columIndex] = CGFloat(yOffset) + height
                    self.columsHeights[columIndex + 1] = CGFloat(yOffset) + height
                }
            }else{
                //if there is not avaiable space
                if(self.avaiableSpaces.count == 0)
                {
                    //  Set column height
                    self.columsHeights[columIndex] = CGFloat(yOffset) + 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)
            column = column < 1 ? column + 1 : 0
            yOffset = column == 0 || getNextCellSize(currentCell: indexPath.row, collectionView: collectionView) == 1 ? yOffset + height : yOffset
        }
    }

    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]
    }
}

结果

enter image description here enter image description here

关于ios - UICollectionViewLayout 小型和大型单元自动布局,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48498009/

相关文章:

ios - 从带有披露指示器的 TableView 单元格 xib 转到 Storyboard

ios - 是否应该在生产 iOS 应用程序中保留断言?

ios - 试图理解 Swift 中的协议(protocol)/委托(delegate)

ios - 使用 Alamofire 解码 json 时出错

ios - 快速自拍时,图像旋转(翻转)

ios - SKAction playSoundFileNamed 停止背景音乐

ios - 如何在 Xcode 的 iOS UI 测试中选择一个选择器 View 项?

ios - UICollectionView didSelectItem 返回错误的索引路径

ios - 带有 invalidateSupplementaryElements 和 UICollectionViewLayoutInvalidationContext 的 invalidateLayout

ios - didSelectItemAtIndexPath 没有在 ios swift 中调用