Showing/Hiding images in CollectionView Cell - swift

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().

Related

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

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.

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
}
}

How to programmatically set up a dynamic number of collection views in Swift

I have a list of many Decks, which is mainly has a name and a list of Cards. The below shows the class structure.
class Deck{
var id:String
var name:String
var cards:[Card]
init(id:String, name:String, cards:[Card]) {
self.id = id
self.name = name
self.cards = cards
}
}
class Card {
var id:String
var name:String
init(id:String, name:String){
self.id = id
self.name = name
}
}
I need to be able to create a single UICollectionView for each deck, where each cell should represent a button with the single card's name, and I need to be able to identify which cell's button is tapped. This means that I need to allow both dynamic number of UICollectionView(list of decks) and a dynamic number of UICollectionViewCell(list of cards). Please note that I already understand how to create the cells dynamically and identify which cell's buttons are tapped.
Overall, the main issue lies in the fact that I won't know how many decks and hence how many UICollectionViews I have to create. But, since the structure across all UICollectionViews are the same but populated with different values (i.e number of cells, cell's label title has different values), I am wondering what is the way to dynamically create UICollectionViews (NOT UICollectionViewCells) and populate them with decks from a list like decks:[Deck]
Thanks a bunch! The image at the bottom shows roughly what I would like to do.
It's still not clear if you really need a collection of UICollectionView objects or you could do with multiple sections in the same collection view. Anyhow, if you do like multiple collection views just make a collection or table view displaying decks list and put collection view for the deck in the cell.
class DeckCell: UICollectionViewCell { // or class DeckCell: UITableViewCell
var deck: Deck = .empty
#IBOutlet weak var collectionView: UICollectionView!
}
extension DeckCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.deck.cards.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath)
cell.card = self.deck.cards[indexPath.row]
return cell
}
}
Here is simple solution for you.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//number of items in Decks Array will be its count
if collectionView == dockCollectionView {
return Decks.count //It will tell the collection view how much cell will be there
}
if collectionView == cardsCollectionView {
return Cards.count
}
}
//Setup your resuable cell here
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//Setup for Dock CollectionView
if collectionView == dockCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Dock Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = Decks[indexPath.row].name
return cell
}
//Setup for Cards CollectionView
if collectionView == cardsCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Cards Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = Cards[indexPath.row].name
return cell
}
}
//Now get to know which cell is tapped
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == dockCollectionView {
print(Decks[indexPath.row].name)
}
if collectionView == cardsCollectionView {
print(Cards[indexPath.row].name)
}
// Here you write a code when user select a particular cell
}
You already have an solution you have a list in which you have a items so you have to implement dynamically so you just need to count a number of item's in a list.
So for do that you use items.count in to func:-
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = item[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Here you write a code when user select a particular cell
}

How can we reload header view inside our collection view

I've been looking for a way to reload my collection view header. So I have a collection view header & a CollectionViewCell that only contains an image. Now when the cell is press, I would like to display the image in the header view without calling collectionView.reloadData(). This is how my didSelectItemAt & didDeselectItemAt method looks like.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedImage = images[indexPath.item]
let imageCell = collectionView.cellForItem(at: indexPath) as! CollectionCell
imageCell.photoBackgroundView.backgroundColor = .red
collectionView.reloadData()
}
override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let imageCell = collectionView.cellForItem(at: indexPath) as! ImagePickerCell
imageCell.photoBackgroundView.backgroundColor = .black
}
So when I select a cell the view turns red, when I deselect it the view turns black. This video here, shows how the behavior without reloading the collectionView. Now here is were I would like to reload the header view.
If I do use collectionView.reloadData(), this is the outcome. How would I be able to reload the header or the collectionView where the header view displays the selected cell image & turns red.
You can try like global instance for that. Like
class YourClass: UIViewController {
/// Profile imageView
var profileImageview = UIImageView()
}
In CollectionView cellforItem assign a imageview. Like
let imageCell = collectionView.cellForItem(at: indexPath) as! CollectionCell
profileImageview = imageCell.imageView
Then when every you selecting collectionViewCell
You can call a function to change a image of imageView. Like
func updateImage() {
profileImageview.image = UIImage()
}
Well I'm trying to understand why sometimes you cast the cell in CollectionCell and sometimes in ImagePickerCell, anyway try to change your functions in these
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! CollectionCell
cell?.photoBackgroundView.backgroundColor = .red
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! CollectionCell
cell?.photoBackgroundView.backgroundColor = .black
}

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
}
}