How can I check if an indexPath is valid, thus avoiding an "attempt to scroll to invalid index path" error?
Solution 1
You could check
- numberOfSections
- numberOfItemsInSection:
of your UICollectionViewDataSource
to see if your indexPath is a valid one.
E.g.
extension UICollectionView {
func isValid(indexPath: IndexPath) -> Bool {
guard indexPath.section < numberOfSections,
indexPath.row < numberOfItems(inSection: indexPath.section)
else { return false }
return true
}
}
Solution 2
A more concise solution?
func indexPathIsValid(indexPath: NSIndexPath) -> Bool {
if indexPath.section >= numberOfSectionsInCollectionView(collectionView) {
return false
}
if indexPath.row >= collectionView.numberOfItemsInSection(indexPath.section) {
return false
}
return true
}
or more compact, but less readable...
func indexPathIsValid(indexPath: NSIndexPath) -> Bool {
return indexPath.section < numberOfSectionsInCollectionView(collectionView) && indexPath.row < collectionView.numberOfItemsInSection(indexPath.section)
}
Solution 3
@ABakerSmith's answer is close, but not quite right.
The answer depends on your model.
If you have a multi-section collection view (or table view for that matter - same issue) then it's pretty common to use an array of arrays to save your data.
The outer array contains your sections, and each inner array contains the rows for that section.
So you might have something like this:
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
In that case, when presented with an indexPath, you'd need to interrogate your model object (myTableViewData, in the above example)
The code might look like this:
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
}
EDIT:
@ABakerSmith has an interesting twist: Asking the data source. That way you can write a solution that works regardless of the data model. His code is close, but still not quite right. It should really be this:
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
}
Solution 4
Using swift extension:
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)
}
Solution 5
Here's a Swift 4 snippet I wrote and have been using for a while. It lets you either scroll to an IndexPath only if it's available, or - throw an error if the IndexPath is not available, to let you control what you want to do in this situation.
Check out the code here:
https://gist.github.com/freak4pc/0f244f41a5379f001571809197e72b90
It lets you do either:
myCollectionView.scrollToItemIfAvailable(at: indexPath, at: .top, animated: true)
Or
myCollectionView.scrollToItemOrThrow(at: indexPath, at: .top, animated: true)
The latter would throw something like:
expression unexpectedly raised an error: IndexPath [0, 2000] is not available. The last available IndexPath is [0, 36]
Related videos on Youtube
webmagnets
I am bible teacher. My second language is Spanish, but I am now learning Chinese so I can teach the bible in that language too.
Updated on May 09, 2020Comments
-
webmagnets about 4 years
How can I check to see whether an
indexPath
is valid or not?I want to scroll to an
indexPath
, but I sometimes get an error if myUICollectionView
subviews aren't finished loading. -
Duncan C about 9 yearsThat won't work because it will return nil if the indexPath exists in your data model but is not currently on-screen.
-
Thorory about 9 yearsI thought that was the problem, namely that the item does exist (in the model), but hasn't finished displaying.
-
Duncan C about 9 yearsHmm. You might be right. It isn't totally clear from the OP's question. Another possibility is simply to make an explicit call to
reloadData
immediately after changing the model. -
Alex Blair over 5 years0 is a valid row and section. Following your logic indexPath will be considered invalid since you assigned a section of 0, while the inverse is true since you're validating an index against a count.
-
Parth Tamane over 3 yearsThis is giving me
EXC_BAD_ACCESS (code=1, address=0xfffffffffffffff8) numberOfRows
:/ (I am using for table view, but I think the concept is same?)