首页 > 解决方案 > 滚动崩溃后的 UICollection 视图“重新加载数据”

问题描述

我使用一个很棒的教程制作了一个集合视图,其中的单元格按列排列。我在主视图控制器的工具栏中添加了一个按钮,该按钮调用collectionView.reloadData()我希望用户能够编辑值,这些值将依次更新数据源,然后重新加载集合视图以显示更新。

在模拟器上运行它可以工作,但如果发生任何滚动,它会导致崩溃*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'。如果没有发生滚动,则调用collectionView.reloadData()有效。我找不到导致崩溃的这个空数组在哪里。我尝试在控制台中打印代码中使用的所有数组,但没有一个似乎是空的。已尝试注释掉多行代码以尝试缩小问题所在,这似乎是问题所在override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?。在调用重新加载数据之前,我还尝试将集合视图框架坐标重置为 0,但这也不起作用。几天来一直被困在圈子里,没有运气。任何关于我哪里出错的建议将不胜感激!到目前为止,我的代码如下(请原谅冗长的解释和代码);

视图控制器

import UIKit

class ViewController: UIViewController {
  
 // variable to contain cell indexpaths sent from collectionViewFlowLayout.
 var cellIndexPaths = [IndexPath] ()
  
  @IBOutlet weak var collectionView: UICollectionView!
  
  @IBOutlet weak var collectionViewFlowLayout: UICollectionViewFlowLayout! {
    
    didSet {
      collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
  }
  
  @IBAction func editButtonPressed(_ sender: UIButton) {
    
    collectionView.reloadData()
  }
  
  @IBAction func doneButtonPressed(_ sender: UIButton) {
  }
  
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    collectionView.delegate = self
    collectionView.dataSource = self
  }
}

extension ViewController:UICollectionViewDataSource {
  
  func numberOfSections(in collectionView: UICollectionView) -> Int {
    2
  }
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    5
  }
  
  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! collectionViewCell
    
    cell.backgroundColor = .green
    cell.textLabel.text = "\(indexPath) - AAAABBBBCCCC"
  
    return cell
  }
}

extension ViewController:UICollectionViewDelegate, IndexPathDelegate {
  
  func getIndexPaths(indexPathArray: Array<IndexPath>) {
    
    cellIndexPaths = indexPathArray.uniqueValues
  }
  
  func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    
    var cellArray = [UICollectionViewCell] ()
    
    print(cellIndexPaths)
   
    for indexPathItem in cellIndexPaths {
    
      if let cell = collectionView.cellForItem(at: indexPathItem) {
        
        if indexPathItem.section == indexPath.section {
          
          cellArray.append(cell)
        }
      }
    
      for cells in cellArray {
        
        cells.backgroundColor = .red
        
        Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { (timer) in
          cells.backgroundColor = .green
      
        }
      }
    }
  }
}


extension ViewController: UICollectionViewDelegateFlowLayout {
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
      
      let cell = collectionView.cellForItem(at: indexPath)
      
      if let safeCell = cell {
        
        let cellSize = CGSize(width: safeCell.frame.width, height: safeCell.frame.height)
        
        return cellSize
      } else {
        
        return CGSize (width: 300, height: 100)
      }
   }
}


extension Array where Element: Hashable {

    var uniqueValues: [Element] {
        var allowed = Set(self)
        return compactMap { allowed.remove($0) }
    }
}

流程布局

import UIKit

protocol IndexPathDelegate {
  
  func getIndexPaths(indexPathArray: Array<IndexPath>)
  
}

class collectionViewFlowLayout: UICollectionViewFlowLayout {
  

  override var collectionViewContentSize: CGSize {
    
    return CGSize(width: 10000000, height: 100000)
  }
  
  override func prepare() {
    

    setupAttributes()
    
    indexItemDelegate()
 
  }
  
  // MARK: - ATTRIBUTES FOR ALL CELLS
  
  private var allCellAttributes: [[UICollectionViewLayoutAttributes]] = []

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

      var layoutAttributes = [UICollectionViewLayoutAttributes]()


      for rowAttrs in allCellAttributes {
          for itemAttrs in rowAttrs where rect.intersects(itemAttrs.frame) {
              layoutAttributes.append(itemAttrs)
          }
      }

      return layoutAttributes
  }
  
  // MARK: - SETUP ATTRIBUTES
  
  var cellIndexPaths = [IndexPath] ()
  
  private func setupAttributes() {
      
      allCellAttributes = []

      var xOffset: CGFloat = 0
      var yOffset: CGFloat = 0

      
      for row in 0..<rowsCount {
          
          var rowAttrs: [UICollectionViewLayoutAttributes] = []
          xOffset = 0
        
        
          for col in 0..<columnsCount(in: row) {
             
              let itemSize = size(forRow: row, column: col)
  
            
              let indexPath = IndexPath(row: row, column: col)
          
          
              let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
              
            
                 attributes.frame = CGRect(x: xOffset, y: yOffset, width: itemSize.width, height: itemSize.height).integral

              rowAttrs.append(attributes)

              xOffset += itemSize.width
            
              cellIndexPaths.append(indexPath)
          
          }

          yOffset += rowAttrs.last?.frame.height ?? 0.0
          allCellAttributes.append(rowAttrs)
      }
  }
  
  
  // MARK: - CONVERT SECTIONS TO ROWS, ITEMS TO COLUMNS
  
  private var rowsCount: Int {
      return collectionView!.numberOfSections
  }

  private func columnsCount(in row: Int) -> Int {
      return collectionView!.numberOfItems(inSection: row)
  }
  
  // MARK: - GET CELL SIZE
  
  private func size(forRow row: Int, column: Int) -> CGSize {
    
      guard let delegate = collectionView?.delegate as? UICollectionViewDelegateFlowLayout,
        
          let size = delegate.collectionView?(collectionView!, layout: self, sizeForItemAt: IndexPath(row: row, column: column)) else {
          assertionFailure("Implement collectionView(_,layout:,sizeForItemAt: in UICollectionViewDelegateFlowLayout")
          return .zero
      }

      return size
  }
  
  private func indexItemDelegate () {
    
    let delegate = collectionView?.delegate as? IndexPathDelegate
    
    delegate?.getIndexPaths(indexPathArray: cellIndexPaths)
  
  }
}

// MARK: - INDEX PATH EXTENSION

//creates index path with rows and columns instead of sections and items

private extension IndexPath {
    init(row: Int, column: Int) {
        self = IndexPath(item: column, section: row)
    }
}

收集单元

import UIKit

class collectionViewCell: UICollectionViewCell {
    
  @IBOutlet weak var textLabel: UILabel!
  
  override func awakeFromNib() {
      super.awakeFromNib()
      
      contentView.translatesAutoresizingMaskIntoConstraints = false
      
      NSLayoutConstraint.activate([
          contentView.leftAnchor.constraint(equalTo: leftAnchor),
          contentView.rightAnchor.constraint(equalTo: rightAnchor),
          contentView.topAnchor.constraint(equalTo: topAnchor),
          contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
      ])
  }
  
}

标签: iosswiftuicollectionview

解决方案


我已经设法通过使用 UICollectionView.reloadSections(sections: IndexSet) 方法解决了这个问题。这不会导致任何崩溃。我遍历所有部分并将每个部分添加到 IndexSet 变量,然后在重新加载部分方法中使用它,如下所示;

 var indexSet = IndexSet()

 let rowCount = collectionView.numberOfSections

 for row in 0..<rowCount {
      
      indexSet = [row]
       
      collectionView.reloadSections(indexSet)
   }

推荐阅读