【问题标题】:UICollectionView autosize and dynamic number of rowsUICollectionView 自动调整大小和动态行数
【发布时间】:2018-11-01 22:02:00
【问题描述】:

我正在尝试做这样的事情:

基本上,我使用的是UICollectionView 和单元格(3 个不同的.xib)。

到目前为止,它有效。

我想做的是:

  1. 设置autoheight
  2. 如果旋转,将 1 行添加到 UIColectionView 2.1 如果是平板电脑,纵向将有 2 行,横向 3 行。 (与第 2 点基本相同,只是增加了 1 行。

我有这样的事情:

extension ViewController {
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator){
    setSizeSize()
}

func setSizeSize(){
    if(DeviceType.IS_IPAD || UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight){
        if let layout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            layout.estimatedItemSize = CGSize(width: 1, height: 1)
            layout.invalidateLayout()
        }
    }else{
        if let layout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
            layout.invalidateLayout()
        }
    }
    myCollectionView.collectionViewLayout.invalidateLayout()
}
}

不起作用。此外,它会冻结设备。在模拟器上工作部分。 (我信任更多设备)

我也试过this,但它有时会起作用...

如果您需要更多信息,请告诉我。

提前感谢大家的帮助。

【问题讨论】:

    标签: swift uicollectionview rows screen-rotation autosize


    【解决方案1】:

    我可以建议您创建自己的UICollectionViewFlowLayout 子类,它会生成所需的布局。这是您可以使用的简单流程布局。如果我遗漏了什么,您可以根据自己的需要进行调整。

    public protocol CollectionViewFlowLayoutDelegate: class {
        func numberOfColumns() -> Int
        func height(at indexPath: IndexPath) -> CGFloat
    }
    
    public class CollectionViewFlowLayout: UICollectionViewFlowLayout {
        private var cache: [IndexPath : UICollectionViewLayoutAttributes] = [:]
        private var contentHeight: CGFloat = 0
        private var contentWidth: CGFloat {
            guard let collectionView = collectionView else {
                return 0
            }
            let insets = collectionView.contentInset
            return collectionView.bounds.width - (insets.left + insets.right)
        }
    
        public weak var flowDelegate: CollectionViewFlowLayoutDelegate?
    
        public override var collectionViewContentSize: CGSize {
            return CGSize(width: self.contentWidth, height: self.contentHeight)
        }
    
        public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            var layoutAttributesArray = [UICollectionViewLayoutAttributes]()
            if cache.isEmpty {
                self.prepare()
            }
            for (_, layoutAttributes) in self.cache {
                if rect.intersects(layoutAttributes.frame) {
                    layoutAttributesArray.append(layoutAttributes)
                }
            }
            return layoutAttributesArray
        }
    
        public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            return self.cache[indexPath]
        }
    
        public override func prepare() {
            guard let collectionView = self.collectionView else {
                return
            }
            let numberOfColumns = self.flowDelegate?.numberOfColumns() ?? 1
            let cellPadding: CGFloat = 8
            self.contentHeight = 0
            let columnWidth = UIScreen.main.bounds.width / CGFloat(numberOfColumns)
            var xOffset = [CGFloat]()
            for column in 0 ..< numberOfColumns {
                xOffset.append(CGFloat(column) * columnWidth)
            }
            var column = 0
            var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
    
            for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
                let indexPath = IndexPath(item: item, section: 0)
                let photoHeight = self.flowDelegate?.height(at: indexPath) ?? 1
                let height = cellPadding * 2 + photoHeight
                let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
                let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
                let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                attributes.frame = insetFrame
                self.cache[indexPath] = attributes
                self.contentHeight = max(self.contentHeight, frame.maxY)
                yOffset[column] = yOffset[column] + height
                column = column < (numberOfColumns - 1) ? (column + 1) : 0
            }
        }
    }
    

    现在在你的 UIViewController 中你可以像这样使用它:

    override func viewDidLoad() {
        super.viewDidLoad()
        let flowLayout = CollectionViewFlowLayout()
        flowLayout.flowDelegate = self
        self.collectionView.collectionViewLayout = flowLayout
    }
    

    在改变方向后使collectionView布局无效

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        guard let flowLayout = self.collectionView.collectionViewLayout as? CollectionViewFlowLayout else {
            return
        }
        flowLayout.invalidateLayout()
    }
    

    现在让您的ViewController 符合CollectionViewFlowLayoutDelegate

    extension ViewController: CollectionViewFlowLayoutDelegate {
        func height(at indexPath: IndexPath) -> CGFloat {
            guard let cell = self.collectionView.cellForItem(at: indexPath) else {
                return 1
            }
            cell.layoutIfNeeded()
            //get calculated cell height
            return cell.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        }
    
        func numberOfColumns() -> Int {
            //how much columns would be shown, depends on orientation
            return UIDevice.current.orientation == .portrait ? 2 : 3
        }
    }
    

    【讨论】:

    • 很好的答案,尽管您可能想解释一下代码的每一行的作用
    • 好答案!我试试看。
    • 效果很好!正是我想要的!谢谢你。唯一的事情是......如果我希望较小的单元格以较大的单元格为中心怎么办?就像图片上的一样......
    • 水平和垂直居中,可以吗?
    • 我现在的想法是,当我重新加载数据时(collectionView.reloadData(),单元格的高度为 1,因为 height 方法......我如何让它工作当我重新加载数据时?
    【解决方案2】:

    这可能是另一种选择:

    protocol MyLayoutDelegate: class {
        func collectionView(_ collectionView: UICollectionView, heightForCellAtIndexPath indexPath: IndexPath, width: CGFloat) -> CGFloat
        func numberOfColumns() -> Int
    }
    
    import UIKit
    
    class MyCollectionViewLayout: UICollectionViewLayout {
    
        weak var delegate: MyLayoutDelegate!
    
    
        public var cellPadding: CGFloat = 5.0
    
        var cellWidth: CGFloat = 190.0
        var cachedWidth: CGFloat = 0.0
        var minimumInteritemSpacing: CGFloat = 10
    
        var numberOfColumns = 1
    
        fileprivate var cache = [UICollectionViewLayoutAttributes]()
        fileprivate var contentHeight: CGFloat  = 0.0
        fileprivate var contentWidth: CGFloat {
            if let collectionView = collectionView {
                let insets = collectionView.contentInset
                return collectionView.bounds.width - (insets.left + insets.right)
            }
            return 0
        }
    
        fileprivate var numberOfItems = 0
    
        override public var collectionViewContentSize: CGSize {
            return CGSize(width: contentWidth, height: contentHeight)
        }
    
        override func prepare() {
    
            guard let collectionView = collectionView else { return }
    
            self.numberOfColumns = delegate.numberOfColumns()
    
            cellWidth = (contentWidth - minimumInteritemSpacing * 2) / CGFloat(numberOfColumns)
    
            let totalSpaceWidth = contentWidth - CGFloat(numberOfColumns) * cellWidth
            let horizontalPadding = totalSpaceWidth / CGFloat(numberOfColumns + 1)
            let numberOfItems = collectionView.numberOfItems(inSection: 0)
            if (contentWidth != cachedWidth || self.numberOfItems != numberOfItems) {
                cache = []
                contentHeight = 0
                self.numberOfItems = numberOfItems
            }
    
            cachedWidth = contentWidth
            var xOffset = [CGFloat]()
            for column in 0 ..< numberOfColumns {
                xOffset.append(CGFloat(column) * cellWidth + CGFloat(column + 1) * horizontalPadding)
            }
            var column = 0
            var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
            for row in 0 ..< numberOfItems {
                let indexPath = IndexPath(row: row, section: 0)
                let cellHeight = delegate.collectionView(collectionView, heightForCellAtIndexPath: indexPath, width: cellWidth)
                let height = cellPadding * 2 +  cellHeight
                let frame = CGRect(x: xOffset[column], y: yOffset[column], width: cellWidth, height: height)
                let insetFrame = frame.insetBy(dx: 0, dy: cellPadding)
                let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
                attributes.frame = insetFrame
                cache.append(attributes)
                contentHeight = max(contentHeight, frame.maxY)
                yOffset[column] = yOffset[column] + height
                if column >= (numberOfColumns - 1) {
                    column = 0
                } else {
                    column = column + 1
                }
            }
    }
    
    override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var layoutAttributes = [UICollectionViewLayoutAttributes]()
        for attributes in cache {
            if attributes.frame.intersects(rect) {
                layoutAttributes.append(attributes)
            }
        }
        return layoutAttributes
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cache[indexPath.item]
    }
    
    }
    

    要使用这个:

    1) 在情节提要中选择您的 collectionView ( Es. myCollectionView ) 并搜索 CollectionViewLayout 2) 点击类检查器并将类更改为 MyCollectionViewLayout。

    3) 在你的 viewDidLoad 中:

    if let layout = myCollectionView?.collectionViewLayout as MyCollectionViewLayout {
            layout.delegate = self
        }
    

    4) 添加两个协议存根:

    func collectionView(_ collectionView: UICollectionView, heightForCellAtIndexPath indexPath: IndexPath, width: CGFloat) -> CGFloat {
        //default height for different layout
        guard let layout = collectionView.collectionViewLayout as? MyCollectionViewLayout else{return 150 // or anything else}
    
        //return what you want, even based on screen size
        return 100
    
    }
    
    func numberOfColumns() -> Int{
       //return number of columns based on screen size
    
       return 1 // or whatever you want
    }
    

    【讨论】:

    • 太好了,谢谢。很抱歉问,但你的解决方案和@Taras Chernyshenko 的解决方案有什么区别?
    猜你喜欢
    • 2017-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-22
    • 2013-03-07
    • 2023-03-08
    • 2017-04-18
    相关资源
    最近更新 更多