How can I fix a UIcollectionViewCell from displaying an empty cell? - swift

I have purposely left an empty image in my assets catalog so that I can get my collectionView to somehow skip that image if it is nil, but so far it will render an empty image in that cell. It is better than crashing my app but how can I get it to skip that image?
here is my cellForItemAt indexPath code. Any ideas would be much appreciated.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: K.collectionViewCell, for: indexPath) as! CollectionViewCell
DispatchQueue.main.async {
let image = RoomModel.roomModel.rooms[indexPath.item]
if let image = image {
cell.imageView.image = image
}
}
return cell
}
Model Solution:
import UIKit
var data = [long list of UIImage(named: ...)]
class RoomModel {
var rooms = data.compactMap { ($0) }
var roomNo = 0
func setRoomNo(sender: Int) {
roomNo = sender
}
func getRoom() -> UIImage {
let image = rooms[roomNo]
return image
}
}
itemForRow Solution:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: K.collectionViewCell, for: indexPath) as! CollectionViewCell
DispatchQueue.main.async {
cell.imageView.image = self.roomModel.rooms[indexPath.item]
}
return cell
}

You would need to modify the data model to not include it if the image is nil and can be done by a simple if check before adding it in the model and then your cell would not render it as it is omitted from the data model.

Related

Showing/Hiding images in CollectionView Cell

I'm trying to show some images and hide others using ".isHidden" in my CollectionView. But when I scroll down or reload the collectionView they either get reordered incorrectly or hidden entirely.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReadBookCell", for: indexPath) as! ReadBookCell
let item = readBookArray[indexPath.item]
for star in cell.starImgOutletCollection {
if star.tag <= item.starRating {
star.isHidden = false
} else {
star.isHidden = true
}
}
return cell
}
Edit: Here is my prepareForReuse
override func prepareForReuse() {
super.prepareForReuse()
for star in starImgOutletCollection {
star.isHidden = true
}
}
Assuming your "stars" are 5 image views in a stack view like this:
And, assuming your starRating will be between 0 and 5 (Zero being no rating yet)...
In your cell class, create a reference to the stack view - since your question mentions starImgOutletCollection I'm assuming you are using #IBOutlet (that is, not creating your views via code), so:
#IBOutlet var starsStackView: UIStackView!
Then, still in your cell class, add this func:
func updateStars(_ starRating: Int) {
for i in 0..<starsStackView.arrangedSubviews.count {
starsStackView.arrangedSubviews[i].isHidden = i >= starRating
}
}
Now, in cellForRowAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReadBookCell", for: indexPath) as! ReadBookCell
let item = readBookArray[indexPath.item]
cell.updateStars(item.starRating)
// do the other stuff to set labels, images, etc
// in the cell
return cell
}
You no longer need the outlet collection for the "star" image views, and you no longer need to implement prepareForReuse().

UICollectionViewCell is populated with previous cells data before new the cell's data loads, need to implement PrepareForReuse()

I have a problem with my UICollectionView, The problem is that if you scroll fast enough (not even that fast) the data that was in the cell before the reuse gets displayed for a second or two before the new data gets displayed.
This is the video of it happening for more context (youtube video link): https://youtu.be/I63hBuxBGI0
This is the code inside of the cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let post = feedElements[indexPath.row] as? FeedPost {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! feedCollectionViewCell
cell.delegate = self
cell.post = post
cell.commentButton.tag = indexPath.row
// assigning all of the data
cell.captionLabel.text = post.caption
cell.locationLabel.text = post.location
cell.timeAgoLabel.text = post.timeAgoString
cell.setUpElements()
cell.prepareForReuse()
return cell
}
}
The code for inside of the numberOfRowsInSection:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return feedElements.count
}
I'm pretty sure what I need to implement is prepareForReuse() but I'm not sure exactly how to do that and couldn't find anything helpful online.
Thanks a lot!
You need to add prepareForReuse in feedCollectionViewCell class like this
class feedCollectionViewCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
// Reset your values here as you want ...
self.post = nil
self.commentButton.tag = 0
// assigning all of the data
self.captionLabel.text = nil
self.locationLabel.text = nil
self.timeAgoLabel.text = nil
}
}

change tableview cell content on collection view cell selection

I have a tableView and collectionView in one view controller.
in tableView I have title description and in collectionView I have lable .
I want on collectionView label selection tableView content should change .
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Bookmark.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionViewBookmark.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! BookMarkCollectionViewCell
cell.lblTitle.text = Bookmark[indexPath.row]
cell.backgroundColor = UIColor.white
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionViewBookmark.cellForItem(at: indexPath)
cell?.backgroundColor = UIColor.blue
self.selectedIndexPath = indexPath
//
let newsDict = arrNewsData[indexPath.row]
if (indexPath.row == 1)
{
let cell1 = tableViewBookMark.cellForRow(at: indexPath) as! BookMarkFirstTableViewCell
cell1.lblTitle.text = newsDict["title"] as! String
tableViewBookMark.reloadData()
}
tableViewBookMark.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewBookMark.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! BookMarkFirstTableViewCell
let dict = arrNewsData[indexPath.row]
cell.lblTitle.text = dict["title"] as! String
// cell.imgBookMark.image = dict["image_url"]
let url = URL(string: dict["image_url"] as! String)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if data != nil{
DispatchQueue.main.async {
let image = UIImage(data: data!)
cell.imgBookMark.image = image
}
}
}.resume()
return cell
}
See my inline comments.
var tempCell: BookMarkFirstTableViewCell?
//Inside cellForRowAt indexPath
tempCell = cell
//Inside (collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
tempCell.lblTitle.text = newsDict["title"] as! String
You're reloading the tableView after updating the values in the cell,
tableViewBookMark.reloadData()
This will trigger the data source function including cellForRowAt, so you will lose your updated values a solution to this is to have a global variable in the UIViewController and check its values inside the cellForRowAt and update it in the collectionView DidSelect .
Extra tip: you don't need to reload all the tableView for a single change you can use
tableView.reloadRows(at: [indexPath], with: .top)
to reload only number of selected cells in the tableView

Why are image views sometimes not appearing in collection view cells?

I just updated to Swift 3.
My collection views were working great before the update. I did the recommended changes to make the compiler happy, but now I'm having this problem.
Image views that are in my custom UICollectionViewCells are simply not appearing anymore. Neither programmatically generated image views nor prototype image views are appearing.
I've given the image views background colors to check if my images are nil. The background colors aren't appearing, so it is safe to assume the image views are not appearing at all.
The cells themselves ARE appearing. Each image view has a label underneath, and the label is displaying properly with the correct text.
The most confusing part is that sometimes the image views DO appear, but there seems to be no rhyme or reason as to why or when.
My code is pretty standard, but I'll go ahead and share it anyway:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return searchClubs.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: HomeCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
cell.barLabel.text = searchClubs[indexPath.row].name
cell.imageCell.image = searchClubs[indexPath.row].image
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = cell.imageCell.frame.height / 2
return cell
}
func feedSearchClubs(child: AnyObject) {
let name = child.value(forKey: "Name") as! String
//Image
let base64EncodedString = child.value(forKey: "Image")!
let imageData = NSData(base64Encoded: base64EncodedString as! String, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters)
let image = UIImage(data:imageData! as Data)
//Populate clubs array
let club = Club.init(name: name, image: image!)
self.searchClubs.append(club)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
Since Xcode 8 you have to call layoutIfNeeded() to calculate size (in your case you need to know cell.imageCell.frame.height) and position from auto layout rules or use a fixed value of cornerRadius.
cell.imageCell.layoutIfNeeded()
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = cell.imageCell.frame.height / 2
OR
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = 5
The imageCell's frame isn't set up ready yet in the cellForItemAt method.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: HomeCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
cell.barLabel.text = searchClubs[indexPath.row].name
cell.imageCell.image = searchClubs[indexPath.row].image
return cell
}
Instead put the setting up of layer on willDisplay since cell.imageCell.frame.height will have its value.
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let cell: HomeCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
cell.imageCell.layer.masksToBounds = true
cell.imageCell.layer.cornerRadius = cell.imageCell.frame.height / 2
}

Toggle image in UICollectionViewCell when cell selected

I have a UICollectionView of items, and I would like an image in a cell to be toggled when the user selects the cell.
I have a custom UICollectionViewCell:
class RDCell: UICollectionViewCell {
var textLabel: UILabel!
var imageView: UIImageView!
var isSelected: Bool!
...(do init and all that good stuff)
}
And selctected item in collection view :
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
let celld = (collectionView.dequeueReusableCellWithReuseIdentifier("collectionCell", forIndexPath: indexPath) as? RDCell)!
let indexPaths = collectionView.indexPathsForSelectedItems()
let newCell = collectionView.cellForItemAtIndexPath(indexPath) as! RDCell
if(celld.selected){
var image: UIImage = UIImage(named: "notSelected")!
newCell.imageView.image = image
newCell.selected = false
}else{
var image: UIImage = UIImage(named: "selected")!
newCell.imageView.image = image
newCell.selected = true
}
return true
}
My attempt partially works, after selecting and unselecting one item I am not able to select it again after, I need to select a different cell before returning to select the first, and this same bug happens on all selected cells.
Any suggestions or another way to implement the functionality I seek would be greatly appreciated.
Swift 4
I just use the property "highlightedImage" in the "cellForItemAt indexPath" method of the collectionView and set another image on it.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = UIColor.clear
let imageView = cell.viewWithTag(1) as! UIImageView
imageView.image = imagesArray1[indexPath.row]
imageView.highlightedImage = imagesArray2[indexPath.row]
imageView.contentMode = .scaleAspectFill
return cell
}
In my case, i assigned a tag in the imageView and call it through it.
Best regards.
You should never call cellForItemAtIndexPath directly. You have no guarantee of what cell you're getting and the changes you make may have no effect.
The proper way to do this is to track your state within the class and change the state of the cell in cellForItemAtIndexPath. Then you simply call collectionView?.reloadData() when you need to update the views.
Simple example for reference:
var selectionTracking: [[Bool]] = []
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
if let selected = selectionTracking[indexPath.section][indexPath.row] {
return selected
}
else{
selectionTracking[indexPath.section][indexPath.row] = false
}
return true
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if let selected = selectionTracking[indexPath.section][indexPath.row] {
selectionTracking[indexPath.section][indexPath.row] = !selectionTracking[indexPath.section][indexPath.row]
}
else{
selectionTracking[indexPath.section][indexPath.row] = true
}
collectionView?.reloadData()
return true
}
My approach will be different for this, i would have used the didDeselectItemAt method of the collectionView...
Swift 3:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let newCell = collectionView.cellForItem(at: indexPath)
newCell.imageView.image = imageAfterSelection[indexPath.row]
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let newCell = collectionView.cellForItem(at: indexPath)
newCell.imageView.image = imagesAfterDeselection[indexPath.row]
}
Swift 4/5:
Inside your collectionViewCell class define a variable called imageName
then override isSelected property to set the image name based on selected state and in collectionView cellForItemAt method set the value for imageName variable for each cell.
CollectionViewCell Class:
class YourCollectionViewCell: UICollectionViewCell
{
#IBOutlet var cellIcon: UIImageView!
var imageName = ""
override var isSelected: Bool {
didSet {
if self.isSelected {
self.cellIcon.image = UIImage(named: "\(self.imageName) Highlighted")
} else {
self.cellIcon.image = UIImage(named: "\(self.imageName) Unhighlighted")
}
}
}
}
CollectionView DataSource:
class YourCollectionVewController: UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// this line is an extension don't copy paste
let cell = collectionView.dequeueReusableCell(with: YourCollectionViewCell.self, for: indexPath)
let imageName = "yourImageName Unhighlighted"
cell.cellIcon.image = UIImage(named: imageName)
cell.imageName = imageName
return cell
}
}