What exactly is the IndexPath of a UICollection

2019-08-25 23:40发布

问题:

I'm having 2 problems with UICollectionViews, and I somehow believe it's related to the IndexPath in both cases, however I don't really know when is the Indexpath called or what exactly it does and when it gets updated or changed.
Anyways, here are my issues: 1. Somehow the Indexpath is 0 multiple times?Youtube VIDEO showing behavior

for this particular behavior here is the code:

extension PointAllocVC: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return (playerArray?.count)!
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "playerCell", for: indexPath) as! PointAllocCell
        cell.cellDelegate = self
        cell.cellIndex = indexPath.row
        cell.nameLabel.text = playerArray![indexPath.row].alias
        cell.scoreLabel.text = String(currentScore[indexPath.row])
        cell.minusBtn.layer.cornerRadius = 15
        cell.plusBtn.layer.cornerRadius = 15
        cell.ptsRemaining = pointsToAllocate
        cell.ptsUsed = abs(pointsUsedInCells[indexPath.row])
        if indexPath.row == 0 {
            cell.isSelf = true
            cell.layer.borderWidth = 5
            cell.layer.borderColor = UIColor.blue.cgColor
        } else {
            cell.isSelf = false
        }
        cell.updatedButtons()
        return cell
    }

As you can see ONLY AT INDEXPATH.ROW == 0 should the cell have a border (because thats' the current user). However when I click the buttons (see button logic below) suddenly they're all index path == 0?

class PointAllocCell: UICollectionViewCell {
    var isSelf: Bool = false
    var cellIndex: Int?
    var ptsRemaining: Int = 0
    var ptsUsed: Int = 0
    var cellDelegate: PointsAllocDelegate?

    @IBAction func plusTapped(_ sender: Any) {
        if cellDelegate != nil {
            cellDelegate!.plusTapReported(fromCell: cellIndex!)
        }
        updatedButtons()
    }

    @IBAction func minusTapped(_ sender: Any) {
        if cellDelegate != nil {
            cellDelegate!.minusTapReported(fromCell: cellIndex!)
        }
        updatedButtons()
    }

    func updatedButtons() {
        switch (ptsRemaining, ptsUsed) {
        case (0,0):
            plusBtn.isEnabled = false
            minusBtn.isEnabled = false
        case (0,_):
            if isSelf{
                plusBtn.isEnabled = false
                minusBtn.isEnabled = true
            } else {
                plusBtn.isEnabled = true
                minusBtn.isEnabled = false
            }
        case (_,0):
            if isSelf{
                plusBtn.isEnabled = true
                minusBtn.isEnabled = false
            } else {
                plusBtn.isEnabled = false
                minusBtn.isEnabled = true
            }
        default:
            plusBtn.isEnabled = true
            minusBtn.isEnabled = true
        }
        pointLabel.text = String(ptsUsed)
    }
}

AND finally the delegate:

extension PointAllocVC: PointsAllocDelegate {
    func plusTapReported(fromCell: Int) {
        if fromCell == 0 {
            //this is self
            pointsToAllocate -= 1
            pointsUsedInCells[fromCell] += 1
        } else {
            pointsToAllocate += 1
            pointsUsedInCells[fromCell] += 1
        }
        reloadCollection()
        reloadLabels()
    }

    func minusTapReported(fromCell: Int) {
        if fromCell == 0 {
            //this is self
            pointsToAllocate += 1
            pointsUsedInCells[fromCell] -= 1
        } else {
            pointsToAllocate -= 1
            pointsUsedInCells[fromCell] -= 1
        }
        reloadCollection()
        reloadLabels()
    }
}

Now. The SECOND ISSUE, I'm getting it when I do the following

playerArray.remove(at: gKPlayerArray.index(of: playerDed)!)

This line of code doesn't do anything, however AFTER that when I call the "reloadData" I get that the IndexPath.row is out of range (for some reason it still thinks that the playerArray is a size X+1 even though I removed an item before calling it.

回答1:

A cell never needs to know its own indexPath. That's a a really bad design.

Refactor your code so you do not pass a row or indexPath to a cell.

Change your cell delegate protocol to pass the actual cell, not an Int.

The class that actually implements the delegate methods will now be told which cell the delegate method is being called on behalf of and if needed, that class can determine the indexPath of the cell.

You also need to update your cellForItemAt.

This code is an issue:

    if indexPath.row == 0 {
        cell.isSelf = true
        cell.layer.borderWidth = 5
        cell.layer.borderColor = UIColor.blue.cgColor
    } else {
        cell.isSelf = false
    }

Whenever you set a property, you must always reset it for the other condition.

    if indexPath.row == 0 {
        cell.isSelf = true
        cell.layer.borderWidth = 5
        cell.layer.borderColor = UIColor.blue.cgColor
    } else {
        cell.isSelf = false
        cell.layer.borderWidth = 0
    }

You don't need to reset the color for a 0 width border.



回答2:

One thing has not been mentioned in answer by @rmaddy - since you are using UICollectionView, you shouldn’t use the row property of IndexPath, but rather the item property. Given the nature of the collection view and its flexibility in layouting cells, the system has no way of discerning „rows”. The row property should be used when working with UITableView.

var row: Int An index number identifying a row in a section of a table view.
var item: Int An index number identifying an item in a section of a collection view.

https://developer.apple.com/documentation/foundation/nsindexpath