简体   繁体   中英

swift collectionview cell didSelectItemAtIndexPath shows wrong cell if you scroll

Let's say you set up a bunch of image views inside a UICollectionView's cells (from an array of image names) and make their alpha 0.5 by default when you set up the items.

Then you make the image view's alpha to 1.0 in the didSelectItemAtIndexPath func, so it becomes alpha 1 when the user taps.

This works when the user taps a cell, but it does not persist if the user scrolls, because the cell is being re-used by the UI on some other level.

The result is another cell farther down the way (when scrolling) becomes alpha 1.0 and the original cell you selected reverts back to its previous alpha 0.5 appearance.

I understand that this is all done to make things more efficient on the device, but I still have not figured out how to make it work properly where the selected item persists.

ANSWER

Apple does provide a selectedBackgroundView for cells that you can use to change the background color, shadow effect, or outline etc. They also allow you to use an image inside the cell with a "default" and "highlighted" state.

Both of those methods will persist with the selection properly.

However, if you wish to use attributes or different elements than one of those provided for indicating your selected state, then you must use a separate data model element that includes a reference to the currently selected item. Then you must reload the viewcontroller data when the user selects an item, resulting in the cells all being redrawn with your selected state applied to one of the cells.

Below is the jist of the code I used to solve my problem, with thanks to Matt for his patience and help.

All of this can be located inside your main UICollectionView Controller class file, or the data array and struct can be located inside their own swift file if you need to use it elsewhere in the project.

Data and data model:

let imagesArray=["image1", "image2", "image3", ...]

struct Model {
    var imageName : String
    var selectedState : Bool

    init(imageName : String, selectedState : Bool = false){
        self.imageName = imageName
        self.selectedState = selectedState
    }
}

Code for the UICollectionView Controller

// create an instance of the data model for images and their status
var model = [Model]()

@IBOutlet weak var collectionView: UICollectionView!

override func viewDidLoad() {
    super.viewDidLoad()

    // build out a data model instance based on the images array
    for i in 0..<imagesArray.count  {
        model.append(Model(imageName: imagesArray[i]))
        // the initial selectedState for all items is false unless otherwise set
    }

}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return imagesArray.count
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    // when the collectionview is loaded or reloaded...

    let cell:myCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! myCollectionViewCell

    // populate cells inside the collectionview with images 
    cell.imageView.image = UIImage(named: model[indexPath.item].imageName)

    // set the currently selected cell  (if one exists) to show its indicator styling
    if(model[indexPath.item].selectedState == true){
            cell.imageView.alpha = 1.0
        } else {
            cell.imageView.alpha = 0.5
    }

    return cell        
}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

    // when a cell is tapped...

    // reset all the selectedStates to false in the data model
    for i in 0..<imagesArray.count {
        model[i].selectedState = false
    }

    // set the selectedState for the tapped item to true in the data model
    model[indexPath.item].selectedState = true

    // refresh the collectionView (triggering cellForItemAtIndexPath above)
    self.collectionView.reloadData()

}

but it does not persist if the user scrolls, because the cell is being re-used by the UI on some other level

Because you're doing it wrong. In didSelect , make no change to any cells . Instead, make a change to the underlying data model , and reload the collection view. It's all about your data model and your implementation of cellForItemAtIndexPath: ; that is where cells and slots (item and section) meet.

Here's a simple example. We have just one section, so our model can be an array of model objects. I will assume 100 rows. Our model object consists of just an image name to go into this item, along with the knowledge of whether to fade this image view or not:

struct Model {
    var imageName : String
    var fade : Bool
}
var model = [Model]()
override func viewDidLoad() {
    super.viewDidLoad()
    for i in 0..<100 {
        // ... configure a Model object and append it to the array
    }
}
override func collectionView(
    collectionView: UICollectionView, 
    numberOfItemsInSection section: Int) -> Int {
        return 100
}

Now, what should happen when an item is selected? I will assume single selection. So that item and no others should be marked for fading in our model. Then we reload the data:

override func collectionView(cv: UICollectionView, 
    didSelectItemAtIndexPath indexPath: NSIndexPath) {
        for i in 0..<100 {model[i].fade = false}
        model[indexPath.item].fade = true
        cv.reloadData()
}

All the actual work is done in cellForItemAtIndexPath: . And that work is based on the model :

override func collectionView(cv: UICollectionView,
    cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let c = self.collectionView!.dequeueReusableCellWithReuseIdentifier(
            "Cell", forIndexPath: indexPath) as! MyCell
        let model = self.model[indexPath.item]
        c.iv.image = UIImage(named:model.imageName)
        c.iv.alpha = model.fade ? 0.5 : 1.0
        return c
}

You logic is incorrect. didSelectItemAtIndexPath is used to trigger something when a cell is selected. All this function should contain is this:

let cell:stkCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! stkCollectionViewCell

cell.imageView.alpha = 1.0

selectedIndex = indexPath.item

Then in your cellForItemAtIndexPath function you should have the logic to set the cell because this is where the cells are reused . So this logic should be in there:

if (indexPath.item == selectedIndex){
    print(selectedIndex)
    cell.imageView.alpha = 1.0
} 
else {
    cell.imageView.alpha = 0.5
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM