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
How to Properly Refresh a Uinavigationbar
Swift Parse - Local Datastore and Displaying Objects in a Tableview
Error "Call Can Throw, But Is Not Marked with 'Try' and the Error Is Not Handled"
Aes 128 Message Decryption -- Swift, iOS
Swift 2 - Separating an Array into a Dictionary with Keys from a to Z
How to Remove Single Object in Array from Multiple Matching Object
Deinit Method Is Not Called in Xcode 10 Beta 6 Playground
Can't Set Calayer's Border Color
Xcode 6 Ignoring Breakpoints - Swift
Uialertcontroller Title and Message Not Appearing
Random Number from an Array Without Repeating the Same Number Twice in a Row
Outline Uilabel Text in Uilabel Subclass