UICollectionViewcell calling functions over and over - swift

I have go this uicollectionviewcell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PostCell
if let CurrentPost = posts[indexPath.row] as? Post{
//determine which constraint to call
if(CurrentPost.PostText != nil){
if(CurrentPost.PostImage != nil){
cell.postImage.image = CurrentPost.PostImage
cell.cellConstraintsWithImageWithText()
}else{
cell.postImage.image = nil
cell.cellConstraintsWithoutImageWithText()
}
}else{
cell.postImage.image = CurrentPost.PostImage
cell.cellConstraintsWithImageWithoutText()
}
}
return cell
}
My goal is to determine which function to goal based on absence or presence of image and text.Now the problem is all this functions get called because some cells do have images cellConstraintsWithImageWithText is being called,others don't have them so cellConstraintsWithoutImageWithText is being called.How can i call a single function for a single cell rather than all cells?

This happens because cells are being reused. The easiest way to handle this is to store index paths of cells with text in your view controller. When cell is dequeued just check if index path is present in stored array and layout accordingly.
In your ViewController
var cellsWithText: [IndexPath] = []
In cellForItemAt indexPath
...
cell.postImage.image = nil
cell.cellConstraintsWithoutImageWithText()
if !cellsWithText.contains(indexPath) {
cellsWithText.append(indexPath)
}
...
Now at the beginning at cellForItemAt indexPath
if let CurrentPost = posts[indexPath.row] as? Post {
if cellsWithText.contains(indexPath) {
// layout for text
} else {
// layout for image
}
I also noticed that your'e using posts[indexPath.row] but your using the collectionView, that doesn't have rows and has item instead. That also might be the issue.

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.

passing data from model to collectionViewCell

I have several custom cells that are applied through this method
switch indexPath.row {
case 1:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "randomCell", for: indexPath)
as? randomCollectionViewCell
case 2:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "timeCell", for: indexPath)
as? timeCollectionViewCell
case 3:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ownerCell", for: indexPath)
as? ownerCollectionViewCell
default:
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath)
as? imageModelCollectionViewCell
}
return cell
}
All cells are displayed simultaneously and sequentially. The last cell that is in the default function is the imageView which I need to pass the value from the model.
The model creates the image as a link, so you also need to upload a picture.
For example this code on like
cell.Image Model.image = ...
throws an error
Value of type 'UICollectionViewCell?'has no member 'modelimage'
This is code from collectionViewCell for what i need to passing data
import UIKit
class imageModelCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var modelImage: UIImageView!
}
How transfer data from model to cell?
//update
i am updating my code by Saleh Altahini post
thank you, I try to implement the second method.
i use var imageModelCell: imageModelCollectionViewCell?
and use method
DispatchQueue.main.async {
self.collectionView.reloadData()
imageModelCell!.modelImage = UIImage(data: data) as imageModelCollectionViewCell }
and have an error
Cannot convert value of type 'UIImage?' to type 'imageModelCollectionViewCell' in coercion
The error you're getting means that your cell is not downcasted to imageModelCollectionViewCell. Maybe you're not referencing the cell correctly?
Anyway, you can setup the cell in two ways.
first method is to setup your cell in the cellForItemAt function like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as! imageModelCollectionViewCell
cell.modelImage.image = //Your Image
return cell
}
Or you can reference your cell at the beginning and set it up later from anywhere else. Just add a variable like var imageModelCell: imageModelCollectionViewCell to the UICollectionViewDataSource and pass the cell in the cellForItemAt like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as! imageModelCollectionViewCell
self.imageModelCell = cell
return cell
}
you can then just use imageModelCell.modelImage = //Your Image from any other function or callback
A side note: it's a good practice to start the name of your classes with a capital letter and variables with a small letter, so you can better differentiate what you're calling or referencing with Xcode. Maybe consider changing the name of you class to ImageModelCollectionViewCell.

Unable to access collectionView cell variables using global variable

Here i made a collectionView cell variable for accessing both objects. But unable to access the objects inside the cell variable
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell: UICollectionViewCell!
if collectionView == collectionView1 {
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellAttachment", for: indexPath) as! AttachmentCell
cell.imgAttachment.image = imageArray1[indexPath.row]
cell.delegate = self
}
else if collectionView == collectionView2 {
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellAttachmentView", for: indexPath) as! AttachmentViewCell
cell.imgFileIcon.image = imgArray2[indexPath.row].fileIcon
}
return cell
Value of type 'UICollectionViewCell?' has no member 'imgAttachment'
The problem lies here.
var cell: UICollectionViewCell!
You have declared the cell to be of type UICollectionViewCell. So, no matter which subclass you store inside it, you cell will only be of type UICollectionViewCell.
You should change it like this,
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView === collectionView1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellAttachment", for: indexPath) as! AttachmentCell
cell.imgAttachment.image = imageArray1[indexPath.row]
cell.delegate = self
return cell
} else if collectionView === collectionView2 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellAttachmentView", for: indexPath) as! AttachmentViewCell
cell.imgFileIcon.image = imgArray2[indexPath.row].fileIcon
return cell
} else {
// Return the proper cell for other cases
}
}
Or, if you are adamant you need only a single return statement at the end of the delegate, then you could do it like this,
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var yourCell: UICollectionViewCell!
if collectionView === collectionView1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellAttachment", for: indexPath) as! AttachmentCell
cell.imgAttachment.image = imageArray1[indexPath.row]
cell.delegate = self
yourCell = cell
} else if collectionView === collectionView2 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellAttachmentView", for: indexPath) as! AttachmentViewCell
cell.imgFileIcon.image = imgArray2[indexPath.row].fileIcon
yourCell = cell
} else {
// Return the proper cell for other cases
}
return yourCell
}
You need to silent the compiler by casting the cell as AttachmentCell. See the below example,
(cell as! AttachmentCell).imgAttachment.image = imageArray1[indexPath.row]
(cell as! AttachmentCell).delegate = self
The reason compiler is not able to recognize the variable is the declaration of variable cell as UICollectionViewCell. As there is no imgAttachment variable in UICollectionViewCell so compiler will complain.

Accessing an array in another view controller

I have an array of NSObjects that I need to read in another viewcontroller. However I'm unsure what level I should be setting the data for it.
This screen shot below best explains what I'm trying to do. Each HomeController has a title, members list, description and inset collectionview (yellow bar). I need the collection views number of cells to equal the number of members.
I tried creating a reference to HomeController inside the inset collectionview by using lazy var but that got the the error:
fatal error: Index out of range
lazy var homeController: HomeController = {
let hc = HomeController()
hc.liveCell = self
return hc
}()
Again this is done from within the inset collectionview
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath :
IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: profileImageCellId, for: indexPath) as! profileImageCell
let room = homeController.rooms[indexPath.row]
print(room.members?.count)
return cell
}
Any suggestions?
EDIT
Data is added to the array using this function
var rooms = [Room]()
func fetchAllRooms(){
Database.database().reference().child("rooms").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let room = Room()
room.rid = snapshot.key
room.setValuesForKeys(dictionary)
self.rooms.append(room)
print(snapshot)
DispatchQueue.main.async(execute: {
self.collectionView?.reloadData()
})
}
print("end of room snap")
}, withCancel: nil)
}
Here is the cell for item at index path at the HomeController level
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell = UICollectionViewCell()
let section = indexPath.section
let liveCell = collectionView.dequeueReusableCell(withReuseIdentifier: LiveCellId, for: indexPath) as! LiveCell
let cell = liveCell
let room = rooms[indexPath.row]
liveCell.liveStreamNameLabel.text = room.groupChatName
liveCell.descriptionLabel.text = room.groupChatDescription
return cell
}
You need to check the count of your array in order to prevent the crash Index out of range
if homeController.rooms.count > indexPath.row {
let room = homeController.rooms[indexPath.row]
print(room.members?.count)
}
Can you Debug and share below two things then we can look further on this
Check whats the index path you are getting
Check if your array have data

Reloading Collection View when returning to previous View Controller

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! LevelSelectCVC
cell.imageView?.image = imageArray[indexPath.row]
if indexPath.item >= stageProgress {
cell.imageView?.image = UIImage(named: "Lock Icon Grey")
} else {
cell.imageView?.image = imageArray[indexPath.item]
}
return cell
}
In this function I check the value of the integer stageProgress and add the same amount of images from an UIImage array to the collection view cells. The remaining of the cells adds a default UIImage. After returning from another view controller I have to check the stageProgress and add another UIImage from the array.
Does anyone know how I can do this? I have tried to do:
self.CollectionView.reloadData()
In both ViewDidAppear and PrepareForSegue
If I restart the game collectionView updates.
Calling ViewDidLoad() in ViewDidAppear resolved the issue.