I basically have a collection view where the cells have a view and that view's alpha decreases when the cell is tapped on. For some reason, when I scroll in the collection view, other cells views are also changing alphas and then the original cell that I selected also changed back. It has something to do with the cellForRowAtIndexPath method, but I'm not entirely sure what the issue is. Here is my code:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("chooseSpace", forIndexPath: indexPath)as! ChooseSpaceCell
let space = spaces2[indexPath.row]
cell.serviceLabel.text = spaces2[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! ChooseSpaceCell
cell.mask.alpha = 0.7
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! ChooseSpaceCell
cell.mask.alpha = 0.25
}
Originally, all the alphas start out at 0.25, change to 0.7 when tapped, and change back when deselected. This is a huge issue so any help would be much appreciated.
When you call
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("chooseSpace", forIndexPath: indexPath)as! ChooseSpaceCell
You are requesting a cell from the collection view. If there is a cell available it will reuse a cell that has already been created. If none are available it creates a new cell.
This means that when you scroll your collection view it is reusing the same cells that you used for previous items. If those items had there opacity changed the new items using that cell will have the same opacity.
You need to add an opacity field to your model or an attribute that will help you compute the opacity.
Related
I have a UICollectionView with flow layout, about 140 cells each with a simple UITextView. When a cell is recycled, I pop the textView onto a cache and reuse it later on a new cell. All works well until I reach the bottom and scroll back up. At that point I can see that the CollectionView vends cell number 85, but then before cell 85 is displayed it recycles it again for cell 87 so I now lose the content of the cell I had just prepared.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FormCell", for: indexPath) as! FormCollectionViewCell
let textView = Cache.vendTextView()
textView.text = "\(indexPath.row)"
cell.addSubview(textView)
cell.textView = textView
return cell
}
And on the UIcollectionViewCelC
override func prepareForReuse() {
super.prepareForRuse()
self.textView.removeFromSuperView()
Cache.returnView(self.textView)
}
I would have thought that after cellForItemAtIndexPath() was called, it would then be removed from the reusable pool of cells but it seems it is immediately being recycled again for a neighbouring cell. maybe a bug or I am possibly misunderstanding the normal behaviour of UICollectionView?
As I understand it, what you're trying to do is just keep track of cell content - save it when cell disappears and restore it when it comes back again. What you're doing can't work well for couple of reasons:
vendTextView and returnView don't take indexPath as parameter - your cache is storing something and fetching something, but you have no way of knowing you're storing/fetching it for a correct cell
There's no point in caching the whole text view - why not just cache the text?
Try something like that:
Have your FormCollectionViewCell just have the text view as subview, and modify your code like so:
class YourViewController : UIViewController, UICollectionViewDataSource, UICollectionViewDelegate
{
var texts = [IndexPath : String]()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FormCell", for: indexPath)
if let formCell = cell as? FormCollectionViewCell {
cell.textView.text = texts[indexPath]
return cell
}
}
func collectionView(_ collectionView: UICollectionView,
didEndDisplaying cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
if let formCell = cell as? FormCollectionViewCell {
{
texts[indexPath] = formCell.textView.text
}
}
}
I'm using a collection view and every time I select the cell I change the cell background color to red. Simple enough:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)! as! CustomCell
cell.backgroundColor = .red
}
This works absolutely fine. When I select the top 3 cells going from left to right, the background color changes exactly as I expect:
However If I reload the collectionView after I select the cell the selection ordering begins to behave strangely. When I select the same top 3 cells in the same order from left to right, different cells become selected:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)! as! CustomCell
cell.backgroundColor = .red
collectionView.reloadData()
}
Apple's documentation is cryptic. https://developer.apple.com/documentation/uikit/uicollectionview/1618078-reloaddata
They say that "This causes the collection view to discard any currently visible items (including placeholders) and recreate items based on the current state of the data source object. " But this makes me think that upon calling reloadData() the collectionViewCells would go back to gray and not jump indexPaths.
Can anyone explain what is going on in reloadData() to make the cell selection at index path ordering so strange?
First You need to use dequeue cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:"CustomCell", for: indexPath) as! CustomCell
And in cell you can use
-(void)prepareForReuse {
// Set default implementation
}
I have UICollectionView and I want to start image animation for only one cell. But the problem is that when i add this animation affects on more item than one cell.
Here is the code:
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return arrays.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! CollectionViewCell
if (indexPath.row == 0)
{
//do the animation
The problem is that the cells are being reused. Think of it this way, if you have 100 different objects but only display 2 at a time, the collectionview will only create, say, about 4 cells. It does this to save memory and to speed up performance
So if you have Cell A and B on screen that show details from array[0] and array[1] respectively and then start scrolling, the next cells that will appear will be C and D that show array[2] and array[3]. However, if you keep scrolling, A and B will appear again, but this time it will show you information for array[4] and array[5]. Then if you keep scrolling, it will show cell C and D again, but with infrmation for array[6] and array[7]. This is the idea of reusing cells
So in your case what is happening is that you're applying an animation to Cell A, but then when you scroll you keep seeing this animation on "other cells". These other cells are in actual fact Cell A being reused.
The solution is for you to stop the animation on a cell whenever cellForItemAtIndexPath: is called i.e
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! CollectionViewCell
/*
Stop animation in case the cell is already being animated
*/
if (indexPath.row == 0)
{
//do the animation
I have a UICollectionView displaying custom cells in a horizontal flow layout; in other words, some content is placed outside the screen bounds.
Additionally, I have a gesture that fires an NSNotification leading to a color change of some of the elements of my cells (i.e. a theme). Everything works perfectly except for the fact that the cells that are present out of the bounds of the screen don't all update to the new color change. Is there any way to force them to redraw?
In the function called when the NSNotification is fired I've tried redrawing the collection view with self.collectionView.reloadData(), self.collectionView.setNeedsDisplay() and self.collectionView.setNeedsLayout but to no avail. I tried the last two of the list in the awakeFromNib() of the custom cell class but nothing.
Here is the code for my cellForItemAtIndexPath:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = popularCollectionView!.dequeueReusableCellWithReuseIdentifier("popular", forIndexPath: indexPath) as! PopularPainting
cell.thumbnail.image = paintings[indexPath.row].paintingImage
cell.name!.text = paintings[indexPath.row].paintingName
cell.price!.setTitle(paintings[indexPath.row].paintingPrice, forState: UIControlState.Normal)
if cell.isDark {
cell.name!.textColor = UIColor(red: 205/255, green: 205/255, blue: 205/255, alpha: 1)
cell.price!.setTitleColor(self.kPaleBlue, forState: .Normal)
self.popularCollectionView.reloadData()
}
return cell
}
Any suggestions?
Note: Scrolling to the offscreen content and repeating the gesture to change themes works perfectly so I have no idea what's up.
Your assumption that there are cells that exist off-screen for every item in your collection view is incorrect. In fact, table views and collection views re-use cells that get scrolled off-screen for new cells coming on-screen, so there are only ever just over a screenful of cells in existence.
You're right to call reloadData after the notification fires. But you need to ensure your implementation of collectionView:itemForRowAtIndexPath: will correctly configure cells that are scrolled on-screen subsequently. That will probably mean saving the state change in a property after the notification fires, and checking that property when you configure cells in collectionView:itemForRowAtIndexPath:.
To get around the "view cannot be reached using cellForItemAtIndexPath:, but will not be recycled before being shown on-screen" issue, you can move the view initialization logic from collectionView:cellForItemAtIndexPath: to collectionView:willDisplayCell:forItemAtIndexPath:.
For example, where you originally had:
override func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: self.reuseIdentifier,
for: indexPath
)
// Initialize cell
return cell
}
You can replace it with:
override func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(
withReuseIdentifier: self.reuseIdentifier,
for: indexPath
)
}
override func collectionView(
_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath
) {
// Initialize cell
}
This ensures that if cellForItemAtIndexPath: returns nil, the cell will be properly initialized by collectionView:willDisplayCell:forItemAtIndexPath: the next time before it is shown on screen.
I'm having a problem with selected items in a collection view.
Selected items change backgroundColor to blue, but it seems like the reusable cells are also affected.
my code looks like this:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath) as UICollectionViewCell?
cell?.contentView.backgroundColor = UIColor.blueColor()
func collectionView(collectionView: UICollectionView, didDeslectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath) as UICollectionViewCell?
cell?.contentView.backgroundColor = UIColor.blackColor()
}
func collectionView(collectionView: UIControllerView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: boxCell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell", forIndexPath: indexPath) as boxCell
cell.cellTitle.text = name[indexPath.row]
}
When I run the application, the selection works, selecting another cell, deselects the other selected cells, but when I scroll, the reusable cells are also turning blue.
I am using a horizontal scroll direction with only 1 row and 4 cells per row.
Where did I go wrong? Anybody else have had this issue?
It's normal behavior - the reason for that is that the items are reused, and after reusing they go through cellForItemAtIndexPath where you are not setting background color, so they keep last one you ever set - in your case didSelect method.
You should create a NSSet where you will keep all selected NSIndexPath's and add / remove NSIndexPath to it when selecting / deselecting.
Setting background color logic should be done in cellForItemAtIndexPath - after you check if NSIndexPath is existing in your NSSet you set a desired color.
On selection you will have to also reload certain element, so it will call cellForItemAtIndexPath for the one you clicked.
Use the prepare for resue method in your cell class.It will work fine.
override func prepareForReuse()
{
self.backgroundColor = UIColor.lightGrayColor()
}