【问题标题】:How can I check if an indexPath is valid, thus avoiding an "attempt to scroll to invalid index path" error?如何检查 indexPath 是否有效,从而避免“尝试滚动到无效索引路径”错误?
【发布时间】:2015-06-13 12:41:02
【问题描述】:

如何检查indexPath 是否有效?

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

【问题讨论】:

    标签: ios swift uicollectionview nsindexpath


    【解决方案1】:

    你可以检查

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

    【讨论】:

    • 这给了我EXC_BAD_ACCESS (code=1, address=0xfffffffffffffff8) numberOfRows :/(我用于表格视图,但我认为这个概念是一样的?)
    【解决方案2】:

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

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

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

    【讨论】:

    • 这不起作用,因为如果 indexPath 存在于您的数据模型中但当前不在屏幕上,它将返回 nil。
    • 我认为是问题所在,即该项目确实存在(在模型中),但尚未完成显示。
    • 嗯。你可能是对的。 OP的问题并不完全清楚。另一种可能性是在更改模型后立即显式调用reloadData
    【解决方案3】:

    @ABakerSmith 的回答很接近,但并不完全正确。

    答案取决于您的型号。

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

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

    所以你可能会有这样的事情:

    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
    }
    

    【讨论】:

      【解决方案4】:

      更简洁的解决方案?

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

      【讨论】:

        【解决方案5】:

        这是我编写并使用了一段时间的 Swift 4 sn-p。 它允许您仅在 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]

        【讨论】:

          【解决方案6】:

          使用快速扩展:

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

          【讨论】:

          • 0 是有效的行和节。遵循您的逻辑 indexPath 将被视为无效,因为您分配了 0 的部分,而反之亦然,因为您正在根据计数验证索引。
          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

            Objective C 版本:

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

            【讨论】:

              【解决方案9】:

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

              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: $0, inDataSource: true) }), with: .top)
              
              // remove
              tableView.deleteRows(at: indexPaths.filter({ tableView.isValid(indexPath: $0) }), with: .top)
              

              输出:

              true or false
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-03-05
                • 2011-09-06
                • 2018-11-29
                • 2013-04-06
                • 2011-05-05
                • 1970-01-01
                相关资源
                最近更新 更多