How do I access a UICollectionViewCell object that isn't yet visible? - ios

My UICollectionViewCell is supposed to change its appearance when it comes into view (just a simple alpha/opacity change on one of the subviews). My code works fine when I scroll (vertically) slowly, but my attempt to access the cell (using cellForItemAt) returns nil when I scroll very quickly. Debugging + research reveals that cellForItem returns nil when the cell object isn't visible yet (even if the object is created), which is what happens when I scroll quickly.
What's the best way to to access the cell object when it's not visible yet?

From your description I'd say that the ideal way to access those cells is in function collectionView:willDisplayCell:forItemAtIndexPath:
That function tells you that the specified cell is just about to be displayed in the collection view.
So you can use it like this:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let cell = cell as? YourCell else { return }
// change alpha etc...
}

Related

UICollectionView cell creation bypassed `willDisplay cell:` call?

I have an UICollectionView with .layer.masksToBounds set to false.
I do this so that way I can achieve a fade-out effect of any cells that scroll out of the collection view's proper bounds.
In order to ensure that I don't display out of bounds cells (when the UICollectionView loads them in), I use the delegate callback:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// pseudo code
if !cell.frame.intersects(collectionView.frame) {
cell.alpha = 0
}
}
However, it appears this callback is not invoked when the CollectionView populates an out-of-bound cell?
While obviously I am always informed if a cell is constructed through the cell provider function (i.e. the function where I invoke the dequeueReusableCell call), I cannot determine whether the cell I construct is out of bounds.
Is there any way I can be informed of when the UICollectionView constructs an out-of-bounds cell?

How to save the status of a cell so when I scroll or leave the page it doesn't refresh the cells?

I'm trying to make a store for a game where you can buy different colors of balls. I'm using a UICollectionView with all white balls to begin with, when I click a cell, it changes the white ball image to a colored ball image (EDIT: an image from a pre made array of colored images). when I scroll down and scroll back up, the cells I selected are reset to the white ball image. I don't want this obviously.
I've tried using the method already built into the UICollectionView class with didSelectItemAt but when I scroll down and back up it gets all messed up (When i select a cell a different one's image is changed not the correct one). I've tried using isSelected in the collectionViewCell class but I can't get the indexpath in here so I can't save which cells are selected.
override var isSelected: Bool{
didSet{
if self.isSelected
{
textImage.image = images[indexPath.item] // I don't know what to put here I don't have the indexPath
}
else
{
textImage.image = #imageLiteral(resourceName: "circleWhite")
}
}
}
Any help is great, I am fairly new to coding in Xcode so some explanation of what to do here is very much appreciated.
EDIT: I have an array of images that should be the store, not just one different color, multiple colors. When I click on a cell, it should access the image in the corresponding index in the array and use that image to replace the white circle.
Did the same thing in our code.Below is the solution for this.
1.Take an array of tuple to maintain selected status and specific colors or what ever you want.
var arrColor = [(isSelected:Bool,color:UIColor)]()
2. Now do the below code on cellForItemAt.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectionViewCell = self.iconCollectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? EditDeviceCollectionViewCell else { return UICollectionViewCell() }
if arrColor[indexPath.item].isSelected{
arrColor[indexPath.item].color = .white
}else {
arrColor[indexPath.item].color = .black
}
return collectionViewCell
}
3.Now write the data source method and use below for color
//MARK:- UICollectionViewDelegate
extension yourViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
arrColor[indexPath.item].isSelected = true
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
arrColor[indexPath.item].isSelected = false.
}
}
Happy Coding 😊
When you scroll off the screen the cells get prepareForReuse called on them. What you need to do is store the state of the color somewhere else - like on the collectionView or a viewModel. And when cellForRow is called you pull the color to show for that row from the saved state variable.
Essentially what’s happening is the cells are being reused when they go off screen to save memory. So when you scroll back to them they are re-created often with the state of another cell since cells are reused.

Multiple UICollectionViews in ViewController | Would not call one CollectionViews Delegate methods

SO I have two UICollectionViews in my UIViewController in Storyboard and both are linked with delegate and datasource to my ViewController. All the associated UICollectionView delegate methods are implemented and checks for the UICollectionViews are implemented. But it's so frustrating that one UICollectionView is getting catered while the other one is getting completely ignored. I have scratched my head in all the available aspects but it is kind of putting me further towards the edge, please help.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.variantsCollectionView {
// let count = (item?.variant_groups?.count)!
return 1
} else {
return 2//(item?.extra_groups?.count)!
}
}
and
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
if collectionView == self.variantsCollectionView {
//IT DOESNT EVEN COME HERE AT ALL
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell_variant", for: indexPath)
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
//HERE IT COMES ALWAYS FOR THE NUMBER OF CELLS
return cell
}
}
Whereas the UICollectionViews are connected like this:
and:
Please please help. Thank you so much
Via comments the TS found solution by following these steps:
Ensure both collection views have non-nil data sources (and delegates).
Check that data source methods are executed for both collection view.
Check that both collection views' cells have valid size.
Finally the problem was found after checking the heights of each collection view inside stack view.
basically CollectionView has a specific height whereas
VariantCollectionView didnt, and both were in a stackView. When first
was created in view it took up the entire size where as the other one
kind of actually disappeared. Hence the issue.

Making iOS Gallery - UICollectionView reusability

I am new to iOS developing and I am using Swift 3.
In my gallery app when user selects a cell, the picture inside that cell displays in a UIImageView above and the cell itself becomes bordered red to show that it is selected right now. However, when I scroll up or down and as soon as the selected cell is destroyed UICollectionView selects another cell from the visible ones.
I want to know how can I restore the selected state of a cell when it reused and prevent UICollectionView from doing that. In conclusion I want to know how to prevent from cell reusabilities effects on selection state.
Sorry for my bad English, not a native speaker.
you can either have an array of the images states if it's true then it's selected else it's not and in the
collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
function you check on the state and do the required setup for the cell or you can make the collection view don't reuse the cells by changing the cell identifier dynamically like this
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeCircularCollectionViewCell\(indexPath.row)", for: indexPath) as? HomeCircularCollectionViewCell
//setup cell
return cell
}
hope my answer helps you in your problem

detecting when an iOS UICollectionCell is going off screen

I'm having a UICollectionView that holds pictures as elements in its datastore.
I want to load a high resolution pic into the element only when its corresponding UICollectionViewCell is currently showing on screen. Later, when the UICollectionViewCell goes off screen, I want to return the element's UIImage into the low resolution version.
My question is, how can I detect when a UICollectionViewCell is going off screen?
(I tried using the prepareForReuse method but I can't predict when it will be called).
I'm currently using a piece of code that sits in scrollViewDidScroll, and every time that the view scrolls I'm checking the self.collectionView.visibleCells to see which cells has scrolled off screen.
It seems a bit of an overhead and I wonder if there is a method called on the UICollectionViewCell itself whenever it is being scrolled of screen ?
The collectionView:didEndDisplayingCell:forItemAtIndexPath: method on UICollectionViewDelegate should do what you want.
From Documentation. collectionView:didEndDisplayingCell is called right after it finishes displaying, not when it goes off screen
Use this method to detect when a cell is removed from a collection view, as opposed to monitoring the view itself to see when it disappears
collectionView:didEndDisplayingCell:forItemAtIndexPath: is the correct method to detect when a cell has gone from screen.
Meanwhile, I think it's more correct not to perform cleanup in collectionView:didEndDisplayingCell:forItemAtIndexPath: but to tell your cell to perform cleanup itself:
func collectionView(_ collectionView: UICollectionView,
didEndDisplaying cell: UICollectionViewCell,
forItemAt indexPath: IndexPath) {
cell.prepareForReuse()
}
With this approach your UICollectionViewDelegate doesn't have to know any implementation details of your UICollectionViewCell subclass. In the cell we'll override prepareForReuse method:
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = lowResolutionImage
highResolutionImage = nil
}

Resources