如何检查 indexPath 是否有效,从而避免 "attempt to scroll to invalid index path" 错误?

How can I check if an indexPath is valid, thus avoiding an "attempt to scroll to invalid index path" error?

如何检查 indexPath 是否有效?

我想滚动到 indexPath,但如果我的 UICollectionView 子视图未完成加载,有时会出现错误。

你可以检查一下

- numberOfSections
- numberOfItemsInSection: 

您的 UICollection​View​Data​Source 以查看您的 indexPath 是否有效。

例如

extension UICollectionView {

    func isValid(indexPath: IndexPath) -> Bool {
        guard indexPath.section < numberOfSections,
              indexPath.row < numberOfItems(inSection: indexPath.section)
            else { return false }
        return true
    }

}

也许这就是您要找的东西?

- (UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath

Return 值: 相应索引路径处的单元格对象,如果单元格不可见或 indexPath 超出范围,则为 nil。

@ABakerSmith 的回答很接近,但不太正确。

答案取决于您的型号。

如果您有一个多部分集合视图(或 table 视图 - 同样的问题),那么使用数组的数组来保存数据是很常见的。

外部数组包含您的部分,每个内部数组包含该部分的行。

所以你可能有这样的东西:

struct TableViewData
{
  //Dummy structure, replaced with whatever you might use instead
  var heading: String
  var subHead: String
  var value: Int
}

typealias RowArray: [TableViewData]

typeAlias SectionArray: [RowArray]


var myTableViewData: SectionArray

在这种情况下,当出现 indexPath 时,您需要查询模型对象(在上例中为 myTableViewData)

代码可能如下所示:

func indexPathIsValid(theIndexPath: NSIndexPath) -> Bool
{
  let section = theIndexPath.section!
  let row = theIndexPath.row!
  if section > myTableViewData.count-1
  {
    return false
  }
  let aRow = myTableViewData[section]
  return aRow.count < row
}

编辑:

@ABakerSmith 有一个有趣的转折点:询问数据源。这样你就可以编写一个不管数据模型如何都能工作的解决方案。他的代码很接近,但仍然不太正确。真的应该是这样的:

func indexPathIsValid(indexPath: NSIndexPath) -> Bool 
{
  let section = indexPath.section!
  let row = indexPath.row!

  let lastSectionIndex = 
    numberOfSectionsInCollectionView(collectionView) - 1

  //Make sure the specified section exists
  if section > lastSectionIndex
  {
    return false
  }
  let rowCount = self.collectionView(
    collectionView, numberOfItemsInSection: indexPath.section) - 1

  return row <= rowCount
}

更简洁的解决方案?

func indexPathIsValid(indexPath: NSIndexPath) -> Bool {
    if indexPath.section >= numberOfSectionsInCollectionView(collectionView) {
        return false
    }
    if indexPath.row >= collectionView.numberOfItemsInSection(indexPath.section) {
        return false
    }
    return true
}

或更紧凑,但可读性较差...

func indexPathIsValid(indexPath: NSIndexPath) -> Bool {
    return indexPath.section < numberOfSectionsInCollectionView(collectionView) && indexPath.row < collectionView.numberOfItemsInSection(indexPath.section)
}

这是我编写并使用了一段时间的 Swift 4 片段。 它允许您仅在 IndexPath 可用时滚动到 IndexPath,或者 - 如果 IndexPath 不可用则抛出错误,以让您控制在这种情况下要执行的操作。

在此处查看代码:

https://gist.github.com/freak4pc/0f244f41a5379f001571809197e72b90

它可以让你做:

myCollectionView.scrollToItemIfAvailable(at: indexPath, at: .top, animated: true)

myCollectionView.scrollToItemOrThrow(at: indexPath, at: .top, animated: true)

后者会抛出如下内容:

expression unexpectedly raised an error: IndexPath [0, 2000] is not available. The last available IndexPath is [0, 36]

使用 swift 扩展名:

extension UICollectionView {

  func validate(indexPath: IndexPath) -> Bool {
    if indexPath.section >= numberOfSections {
      return false
    }

    if indexPath.row >= numberOfItems(inSection: indexPath.section) {
      return false
    }

    return true
  }

}

// Usage
let indexPath = IndexPath(item: 10, section: 0)

if sampleCollectionView.validate(indexPath: indexPath) {
  sampleCollectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: true)
}

如果您在不知道索引路径是否有效的情况下尝试设置集合视图中单元格的状态,您可以尝试保存具有特殊状态的单元格的索引,并设置加载单元格时。

Objective C 版本:

- (BOOL)indexPathIsValid:(NSIndexPath *)indexPath
{
    return indexPath.section < [self.collectionView numberOfSections] && indexPath.row < [self.collectionView numberOfItemsInSection:indexPath.section];
}

您应该检查将附加数据源的索引路径的验证(未来状态)和删除当前存在的索引路径(当前状态)。

extension UITableView {

func isValid(indexPath: IndexPath, inDataSource: Bool = false) -> Bool {
    guard
        let numberOfSections = inDataSource
            ? dataSource?.numberOfSections?(in: self)
            : numberOfSections,
        let numberOfRows = inDataSource
            ? dataSource?.tableView(self, numberOfRowsInSection: indexPath.section)
            : numberOfRows(inSection: indexPath.section)
    else {
        preconditionFailure("There must be a datasource to validate an index path")
    }
    return indexPath.section < numberOfSections && indexPath.row < numberOfRows
}

用法:

// insert    
tableView.insertRows(at: indexPaths.filter({ tableView.isValid(indexPath: [=11=], inDataSource: true) }), with: .top)

// remove
tableView.deleteRows(at: indexPaths.filter({ tableView.isValid(indexPath: [=11=]) }), with: .top)

输出:

true or false