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?
Related
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...
}
I will create a carousel using UICollectionView.
And I want the cells outside the bounds of UICollectionView would be visible.
So I added this code because I thought it's an easy problem.
collectionView.isPagingEnabled = true
collectionView.clipsToBounds = false
But, Of course, some cells outside the bounds were hidden by reuse.
Is there anyway to use UICollectionView without reuse, or to adjust the reuse range, or ...etc?
Thanks in advance.
yes you can in fact use UICollectionView without reuse.
in collectionview's datasource function :
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
instead of using
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! CollectionViewCell
and returning the cell, you can create your own cell without calling collectionView's dequeue function and return this newly created cell
the downside to this is everytime your collectionview need a new cell, it will creates a new cell and not reusing the one you already had, resulting in overtime increasing memory hogging cells
to solve this, you can arrange an array that keep track of which cell to use on which index, and fetch cell from this array (if its available) or create one when its not and append it to the array
I am implementing self sizing UITableViewCell containing UICollectionView which extends its height based on the number of items received from API.
The views are structured like this
UITableView
UITableViewCell
UICollectionView
UICollectionViewCell
UICollectionViewCell
UICollectionViewCell
When the UICollectionView receives new data, UITableViewCell frame is updated with the following code
layoutIfNeeded()
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutIfNeeded()
collectionView.snp.updateConstraints { make in
make.height.equalTo(collectionView.collectionViewLayout.collectionViewContentSize).priority(.high)
}
The problematic part is adding pagination on scroll to the bottom of the UICollectionView. The self sizing code causes that willDisplay cell is called for each cell of the UICollectionView causing // Fetch more data request to trigger, which fetches new data, updates UICollectionView height, triggering the API request again, and it's an infinite loop.
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath.item == collectionView.numberOfItems(inSection: indexPath.section) - 1 {
// Fetch more data
}
}
Any idea how to avoid this cycle? Thanks.
I have a vertical UICollectionView with flow layout and I'm trying to implement an infinite scroll behaviour. When the view comes near to the end, the api request 10 more registers on the server side and then I update the collection data. But when I do that my table don't keeps on the same cell I was before the reload. How can I keep the position after a reload?
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath.row == viewModel.numberOfItemsInSection - 3 {
self.viewModel.fetchNextRepositoriesPage() { shouldUpdateData in
self.repositoriesListView.repositoriesCollectionView.reloadData()
self.repositoriesListView.layoutIfNeeded()
}
}
}
private func reloadCollectionData() {
let offset = collectionView.contentOffset
collectionView.reloadData()
view.layoutIfNeeded()
collectionView.contentOffset = offset
}
EDIT:
I noticed that this problem only occurs when I use estimadedItemSize on my collectionView. When the itemSize is a constant the problem does not occur.
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
}