I've a collectionview that I use as an image gallery, its cell fill all its size and there's only an imageview inside it for each row.
I load its datasource as follow:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageSliderCell", for: indexPath) as! SliderCell
// Configure the cell
let url = URL(string: ad.images![indexPath.row].small_url!)
cell.image.kf.setImage(with: url)
return cell
}
The problem is: sometimes when the collectionview finish loading it starts displaying the first photo in the array(as it should works) but other times it starts from a different index, starting for example from the third photo as if I use something like collectionview.scrolltoindex(3).
Related
I am learning ios coding and reading a book. There is something about the indexPath of a UICollectionViewCell that I don't understand. The UICollectionView displays images that are fetched using the remoteUrl attribute of the Photo Object with URLSessionDataTask. An escaping compeletion handler is passed to the image-fetching function to update the UIImageView inside the cell.
The comment of the code snippet on the book says that "The index path for the photo might have changed between the time the request started and finished". Why does that happen?
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let photo = photoDataSource.photos[indexPath.row]
// Download the image data, which could take some time
photoStore.fetchImage(photo: photo) { (result) in
// The index path for the photo might have changed between the
// time the request started and finished, so find the most
// recent index path
guard
let photoIndex = self.photoDataSource.photos.firstIndex(of: photo),
case let .success(image) = result
else {
return
}
let photoIndexPath = IndexPath(row: photoIndex, section: 0)
// When the request finishes, only update the cell if it's still visible
if let cell = collectionView.cellForItem(at: photoIndexPath) as? PhotoCollectionViewCell {
cell.updateImage(image: image)
}
}
}
Read about reuse cell for tableView, or for collectionView
Example:
add code
In viewDidLoad:
collectionView.register(UICollectionViewCell.self, forCellReuseIdentifier: "Your Reuse Identifier")
In Extension of your viewController (UICollectionViewDelegateFlowLayout)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Reuse Identifier", for: indexPath)
cell.imageView.image = photos[indexPath.row]
return cell
}
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 am having a problem with changing the text of a label when a collection view cell is tapped. I have tried using didSelectItemAt and didHighlightItemAt but nothing worked. Here's what my cell looks like:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.subjectName.text = "Selected"
}
You need
let cell = collectionview.cellForItem(at: indexPath) as! CollectionViewCell
cell.subjectName.text = "Selected"
but note because of cell dequeuing this change is temporary when the cell is still shown if you scroll around you may find another text inside that index , so reflect the changes in the array model of the collection and reload that indexPath
var statesArr = ["Selected","Default",,,,,,,,,,]
inside didSelectItemAt
statesArr[indexPath.row] = "Selected"
self.collectionView.reloadItems(at:[indexPath])
inside cellForItemAt
let cell = ///
cell.subjectName.text = statesArr[indexPath.row]
I'm very new to iOS development and Swift.
I have a UICollectionView that populates images from the array. My idea is to load initially 10 images and let user scroll to the bottom of the collectionView and then load next 10 images from the array by pressing button 'more' or just automatically.
Can you suggest the best way to do it?
My code is:
self.searches.insert(results, at: 0)
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//1
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
for: indexPath) as! CatPhotoCell
// this methods takes one photo from searches array
let flickrPhoto = photoForIndexPath(indexPath: indexPath)
cell.imageView.image = flickrPhoto.thumbnail
cell.cat_name.text = flickrPhoto.title
return cell
}
private extension CatsPhotosCollectionViewController {
func photoForIndexPath(indexPath: IndexPath) -> FlickrPhoto {
return searches[(indexPath as NSIndexPath).section].searchResults[(indexPath as IndexPath).row]
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){
//step 2 here
}
1.Intially set array count as 10.
2.Increase array count whatever you want inside the above function and reload collectionView.
I'm trying to implement a custom cell with support for user tapping. Previously the functions related are:
func collectionView(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueResuableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.setEmpty() // to init the cell
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as ! CustomCell
//implementations
self.collectionView.reloadItem(at: [indexPath])
}
Then I noticed that after the tapping, the second function gets called first, but the first one also gets called afterwards, which means after tapping my cell will still be set to empty, so I changed the first function to this:
let cell = collectionView.dequeueResuableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
if cell.valueInside == nil {
cell.setEmpty() // this will set valueInside to be a non-nil value
}
return cell
But it's still not working properly. I tracked the process: when loading the UI for the first time, init cell first (with the setEmpty() method); then after the tapping, cell is updated, and then the first function is called, but the cell obtained by this
collectionView.dequeueResuableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
shows that the value inside is still nil, so the cell is not really up-to-date. How should I fix this? Or is my implementation logical (should I init the cell somewhere else instead of using this
check if it's nil -> then init
logic)?
func collectionView(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
is called every time your inquire for a cell like
collectionView.cellForItem(at: indexPath)
or
self.collectionView.reloadItem(at: [indexPath])
The best way to use is to declare a class level variable like an array to hold the backed data, then for cell creating get data from that array and in your didSelectItemAt update that array and then just force the collection view to update that cell only.
// In your class scope
private var arData: [String] = ["One", "Two", "Three"] // This could be populated in viewDidLoad as well
// .... rest of your code
func collectionView(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueResuableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
//...
if indexPath.row < arData.count {
cell.valueInside = arData[indexPath.row]
} else {
cell.valueInside = "Default Value"
}
return cell
}
// ....
func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//implementations
if indexPath.row < arData.count {
arData[indexPath.row] = "newValue"
self.collectionView.reloadItem(at: [indexPath])
}
}
This is the logic on how to correctly change a cell view content now if you want to change it's appearance again you should set the flag or whatever status distinguishing mechanism you have in the didSelectItemAt and just reload the cell considering cellForItemAt will refer to that status and apply's that appearance change.