Scrolling in Uicollectionview Selects Wrongs Cells - Swift

Scrolling in UICollectionView selects wrongs cells - Swift

You don't want to call cellForItemAtIndexPath and configure the cells in the didSelectItemAtIndexPath or didDeselectItemAtIndexPath delegate methods.

Also, you shouldn't be calling selectItemAtIndexPath and deselectItemAtIndexPath from within the cellForItemAtIndexPath method.

Instead, just keep track and toggle the state of the selected category in your select/deselect callbacks, and then don't do anything other the set up up the look of your cells in cellForItemAtIndexPath.

As the commenter pointed out, the cells are re-used, so stick to the simple way the delegate callbacks are designed to be used, and you should have much better luck.

If you need to refresh the look of the cells, do it by relying on cellForItemAtIndexPath being called while scrolling and using the reloadData and reloadItemsAtIndexPaths collection view methods if you need to force an update.

swift collectionview cell didSelectItemAtIndexPath shows wrong cell if you scroll

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
}

UICollectionView shows wrong cell selected issue

Using tags is a very bad idea, especially here beacause you defined you tags on the prototype cell so it's the same for each cell.
You must create a custom collectionViewCell class and link the labels and imageView with IBOutlet so you can access these normally :

CustomCollectionViewCell *cell = (CustomCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.label1.text = [_arraySubCategory objectAtIndex:indexPath.row];
cell.label2.text = [_arrayID objectAtIndex:indexPath.row];

Check this tutorial, it explains step by step how you should do it.

CollectionViewCell scrolls to wrong position

I found the answer here.

I had to add this line to my collectionView:

collectionView.contentInsetAdjustmentBehavior = .never

UICollectionView displays wrong images in cells

Both UICollectionViewCell and UITableViewCell are reused. As one scrolls off the top of the screen, it is reinserted below the visible cells as the next cell that will appear on screen. The cells retain any data that they have during this dequeuing/requeuing process. prepareForReuse exists to give you a point to reset the view to default values and to clear any data from the last time it was displayed. This is especially important when working with asynchronous processes, such as network calls, as they can outlive the amount of time that a cell is displayed. Additionally, you're doing a lot of non-setup work in awakeFromNib. This method is not called every time a cell is displayed, it is only called the FIRST time a cell is displayed. If that cell goes off screen and is reused, awakeFromNib is not called. This is likely a big reason that your collection views have the wrong data, they're never making their network request when they appear on screen.

EcardsCategoriesTableViewCell:

prepareForReuse should be implemented. A few things need to occur in this method:

  • theseEcards should be nilled. When a table view scrolls off screen, you want to get rid of the collection view data or else the next time that cell is displayed, it will show the collection view data potentially for the wrong cell.
  • You should keep a reference to the dataTask that runs in awakeFromNib and then call cancel on this dataTask in prepareForReuse. Without doing this, the cell can display, disappear, then get reused before the dataTask completes. If that is the case, it may replace the intended values with the values from the previous dataTask (the one that was supposed to run on the cell that was scrolled off screen).

Additionally, the network call needs to be moved out of awakeFromNib:

  • You are only ever making the network call in awakeFromNib. This method only gets called the first time a cell is created. When you reuse a cell, it is not called. This method should be used to do any additional setup of views from the nib, but is not your main entry point in adding data to a cell. I would add a method on your cell that lets you set the category id. This will make the network request. It will look something like this:

    func setCategoryId(_ categoryId: String) {
    DispatchQueue.main.async {
    let jsonUrlString = "https://**********/*******/content?category=\(categoryId)"
    guard let url = URL(string: jsonUrlString) else { return }
    URLSession.shared.dataTask(with: url) { (data, response, err) in
    guard let data = data else { return }

    if err == nil {
    do {
    let decoder = JSONDecoder()
    let ecards = try decoder.decode(Ecards.self, from: data)
    self.theseEcards = ecards.content
    self.theseEcards = self.theseEcards.filter{$0.isActive}

    } catch let err {
    print("Err", err)
    }

    DispatchQueue.main.async {
    self.thisEcardCollection.reloadData()
    }
    }
    }.resume()
    }
    }

This will be called in the cellForRowAt dataSource method in EcardsViewController.

EcardCategoriesCollectionViewCell:

This cell has similar issues. You are setting images asynchronously, but are not clearing the images and cancelling the network requests when the cell is going to be reused. prepareForReuse should be implemented and the following should occur within it:

  • The image on the image view should be cleared or set to a default image.
  • The image request should be cancelled. This is going to take some refactoring to accomplish. You need to hold a reference to the dataTask in the collection view cell so that you can cancel it when appropriate.

After implementing these changes in the cells, you'll likely notice that the tableview and collection view feel slow. Data isn't instantly available. You'll want to cache the data or preload it some way. That is a bigger discussion than is right for this thread, but it will be your next step.



Related Topics



Leave a reply



Submit