UICollectionViewLayout '冻结'

Posted

技术标签:

【中文标题】UICollectionViewLayout \'冻结\'【英文标题】:UICollectionViewLayout 'freezes'UICollectionViewLayout '冻结' 【发布时间】:2017-04-24 19:58:48 【问题描述】:

我是一个 swift/ios 新手,我开始认为我已经将珠穆朗玛峰作为我的第一个任务。无论如何,我的应用程序都有一个基于周的滚动日历视图。我正在使用自定义UICollectionViewLayout。当应用程序启动时,一切看起来都很好,我可以向下滚动 24 小时和几天(见第一张图片)。

当用户达到预定义的单元格数(天)的末尾时,更多的单元格会添加到数据源中。我可以愉快地连续向右滚动,代码会根据需要添加更多单元格。

问题:

当我在将任何其他数据(天数)添加到数据源之后停止滚动,然后重新开始滚动时,布局会冻结在一个块中并只是浮动(参见第二张图片)。

将单元格正确放置(固定)到视图顶部不再起作用(因为它只是前面提到的冻结单元格块)并且不会显示新单元格。

奇怪的是,在布局冻结后,终端继续从UICollectionViewLayout 注销调试,将单元格放置在正确的位置,尽管UICollectionViewController 似乎刚刚停止收听?

在终端中打印“日期”单元格:Cell attributes: <UICollectionViewLayoutAttributes: 0x6080003edf00> index path: (<NSIndexPath: 0x60800003a760> length = 2, path = 2 - 0); frame = (1033 235; 47 50); zIndex = 1024; 

来自 View Hierarchy 调试:(UICollectionView): <UICollectionView: 0x7feb7302d000; frame = (0 0; 667 375); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x600000051490>; layer = <CALayer: 0x6000000329a0>; contentOffset: 1033, 235; contentSize: 2651, 910> collection view layout: <V3.CDCLayout: 0x7feb7140f1a0>

来自“日期”单元格的视图层次结构调试:<V3.CDCMonthCell: 0x7feb7410a690; baseClass = UICollectionViewCell; frame = (1201.5 361; 47 50); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x608000035180>>

[更新]

嗨 Rob - 我尝试剥离我的代码,然后还遵循了几个教程,当我添加我的数据源(单例)时,这些教程都以同样的问题结束 - 所以有一个热门嫌疑人(!)但我就是想不通它。下面的代码基于一个教程示例,但也出现了同样的问题。

CollectionViewController:

private let reuseIdentifier = "cCell"

class CustomCollectionViewController: UICollectionViewController 

    let localItems = Items.items.getItems()


    override func viewDidLoad() 
        super.viewDidLoad()
    

    override func didReceiveMemoryWarning() 
        super.didReceiveMemoryWarning()
    

    override func numberOfSections(in collectionView: UICollectionView) -> Int 
        return 6  


    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return Items.items.getItemCount()
    

    override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) 

        if (Items.items.getItemCount() - indexPath.row ) == 2 
            Items.items.addItems()
            collectionView.reloadData()
            collectionView.collectionViewLayout.invalidateLayout()
        
    

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> CustomCollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CustomCollectionViewCell

        cell.textLabel.text = "SEC=\(indexPath.section),IXDI=\(indexPath.item)"

        return cell
    

布局:

class CustomCollectionViewLayout: UICollectionViewLayout 

    let CELL_HEIGHT = 145.0
    let CELL_WIDTH = 145.0
    let STATUS_BAR = UIApplication.shared.statusBarFrame.height
    var cellAttrsDictionary = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()
    var contentSize = CGSize.zero

    override var collectionViewContentSize : CGSize 
        return contentSize
    

    override func invalidateLayout() 
        super.invalidateLayout()
        cellAttrsDictionary.removeAll()
    

    override func prepare() 

        if (collectionView?.numberOfSections)! > 0 
            for section in 0...collectionView!.numberOfSections-1 

               if (collectionView?.numberOfItems(inSection: section))! > 0 
                    for item in 0...collectionView!.numberOfItems(inSection: section)-1 

                        let cellIndex = IndexPath(item: item, section: section)
                        let xPos = Double(item) * CELL_WIDTH
                        let yPos = Double(section) * CELL_HEIGHT

                        let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
                        cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)

                        if section == 0 && item == 0 
                            cellAttributes.zIndex = 4
                         else if section == 0 
                            cellAttributes.zIndex = 3
                         else if item == 0 
                            cellAttributes.zIndex = 2
                         else 
                            cellAttributes.zIndex = 1
                        

                        cellAttrsDictionary[cellIndex] = cellAttributes
                    
                
            
        

        let contentWidth = Double(collectionView!.numberOfItems(inSection: 0)) * CELL_WIDTH
        let contentHeight = Double(collectionView!.numberOfSections) * CELL_HEIGHT
        self.contentSize = CGSize(width: contentWidth, height: contentHeight)
    

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

        for cellAttributes in cellAttrsDictionary.values 
            if rect.intersects(cellAttributes.frame) 
                attributesInRect.append(cellAttributes)
            
        
        return attributesInRect
    

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? 
        return cellAttrsDictionary[indexPath]!
    

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

项目/数据源/单例:

class Items 

    static var items = Items() // Singleton

    var classItems = [Int]()

    init() 
        addItems()
    

    func getItemCount() -> Int 
        return classItems.count
    

    func getItems() -> [Int] 
        return classItems
    

    func addItems() 
        for i in 1 ... 7 
            classItems.append(i)
        
    


【问题讨论】:

没有代码将很难诊断。我建议您创建问题的minimal, complete, verifiable example (MCVE),然后发布该 MCVE 的图像和代码。但是这里还不足以让我们了解出了什么问题,我们绝对不想看到生成这些图像的所有代码。我们需要带有代码的 MCVE。 感谢您查看此 Rob。关于 MCVE 的好建议。让我来做。 顺便说一句,如果您的集合视图似乎没有更新,即使您有证据表明应用程序没有死锁,我会留意后台线程中的任何意外 UI 更新(要么是您手动分派到后台队列的东西,要么是在后台队列上运行的URLSession 完成处理程序中的东西)。确保您在任何后台线程上没有任何 reloadData 或类似调用。 谢谢罗伯。到目前为止,一切都应该在主线程上。 Rob - 我更新了代码 MCVE。希望你能明白这一点:o) 【参考方案1】:

在我的测试中,willDisplay 中数据源的重新加载似乎是问题的根源。通过DispatchQueue.main.async ... ,问题似乎消失了。

private var lastReloadedRow = 0

override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) 
    if (Items.items.getItemCount() - indexPath.row) == 2 && lastReloadedRow < indexPath.row 
        lastReloadedRow = indexPath.row
        DispatchQueue.main.async 
            Items.items.addItems()
            collectionView.reloadData()
        
    

注意,我添加了一个 state 属性来跟踪我们是否已经加载,因为它的方式是不必要地重新加载多次。


为了完整起见,这是我对您的自定义布局的完整实现。我做了一些不相关的事情:

简体layoutAttributesForElements(in:);和 已关闭 shouldInvalidateLayout(forBoundsChange:),因为您重建字典的过程超出了必要。

因此:

class CustomCollectionViewLayout: UICollectionViewLayout 

    let CELL_HEIGHT = 145.0
    let CELL_WIDTH = 145.0
    let STATUS_BAR = UIApplication.shared.statusBarFrame.height
    var cellAttrsDictionary = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()
    var contentSize = CGSize.zero

    override var collectionViewContentSize : CGSize 
        return contentSize
    

    override func invalidateLayout() 
        super.invalidateLayout()
        cellAttrsDictionary.removeAll()
    

    override func prepare() 
        if collectionView!.numberOfSections > 0 
            for section in 0 ..< collectionView!.numberOfSections 
                if collectionView!.numberOfItems(inSection: section) > 0 
                    for item in 0 ..< collectionView!.numberOfItems(inSection: section) 
                        let cellIndex = IndexPath(item: item, section: section)
                        let xPos = Double(item) * CELL_WIDTH
                        let yPos = Double(section) * CELL_HEIGHT

                        let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
                        cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)

                        cellAttrsDictionary[cellIndex] = cellAttributes
                    
                
            
        

        let contentWidth = Double(collectionView!.numberOfItems(inSection: 0)) * CELL_WIDTH
        let contentHeight = Double(collectionView!.numberOfSections) * CELL_HEIGHT
        contentSize = CGSize(width: contentWidth, height: contentHeight)
    

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        return cellAttrsDictionary.values.filter  $0.frame.intersects(rect) 
    

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? 
        return cellAttrsDictionary[indexPath]!
    

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

【讨论】:

协议似乎排除了谢谢;但是,嘿 - 应得的功劳 - 伟大的工作。对附加调音有很好的了解。

以上是关于UICollectionViewLayout '冻结'的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionViewLayout '冻结'

UICollectionViewLayout 示例

UICollectionViewLayout 教程

我应该继承啥 UICollectionViewLayout 或 UICollectionViewFlowLayout

使用缓存的 UICollectionViewLayoutAttributes 子类化 UICollectionViewLayout

UICollectionViewLayout - 2 列中的 3 个单元格