Array of Photos from Assets Cassets displayed in CollectionViewCells - swift

I am having some trouble displaying images from the assetsCassets into my collectionViewCells.
I have 4 images named "Avenues", "Streets", "Hotels", "Restaurants"
My code for instantiating the array with the images is the following:
var arrayFotos = ["Avenues", "Streets", "Hotels", "Restaurants"]
And then to display it on the collectionViewCell, I do the following code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCellFotos", for: indexPath) as! FotosCollectionViewCell
for image in arrayFotos {
cell.imgFoto.image = UIImage(named: image)
}
return cell
}
However, it is only returning one of my four images. How could I display the four images into my collectionViewCell??

I presume that it is only displaying the "Restaurants" image. This is because you are running a for loop, resetting the image to be displayed by cell.imgFoto, starting first with "Avenues" and then ending with "Restaurants".
To fix this, it depends what are you trying to accomplish. Are you trying to display all four images in one cell? Or have four different cells, each with one image?
If you want the former, then you need to create four different image views in your cell, and then you can set them like this in the cellForItemAt method:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCellFotos", for: indexPath) as! FotosCollectionViewCell
cell.imgFoto1.image = UIImage(named: arrayFotos[0])
cell.imgFoto2.image = UIImage(named: arrayFotos[1])
cell.imgFoto3.image = UIImage(named: arrayFotos[2])
cell.imgFoto4.image = UIImage(named: arrayFotos[3])
return cell
If you want the latter, then you need to return 4 cells or arrayFotos.count (assuming you have only one section) in the func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int method.
Then, you can do the following in the cellForItemAt method:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCellFotos", for: indexPath) as! FotosCollectionViewCell
cell.imgFoto.image = UIImage(named: arrayFotos[indexPath.row])
return cell
This will create four different cells, each has its own image view set by the data in arrayFoto depending on the indexPath.row.

Related

Where do I put computations for reusable cells?

In my view controllers, a lot of my cellForItemAt methods have computations in them. In the following example, I get the amount, image, and balanceAtDate and pass them into my view:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SummaryCell.cellId, for: indexPath) as! SummaryCell
let amount = filteredTransactions[indexPath.row].transactionAmount
let image = UIImage(named: filteredTransactions[indexPath.item].transactionCategory!.categoryName)
let balanceAtDate: Double = realm.objects(Transaction.self).filter("transactionDate <= %#", transactionResults[indexPath.item].transactionDate).sum(ofProperty: "transactionAmount")
cell.configure(with: image, and: amount, and: balanceAtDate)
return cell
}
It has come to my attention that I shouldn't make any computations or calculations in cellForItemAt because it is called for every cell and it will slow down the application.
I create 3 constants: amount, image, and balanceAtDate. Do I need to put all three somewhere else, or just the balanceAtDate?
Where, then, do I put a computation that requires the indexPath.item to get something like the balanceAtDate?

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.

Adding placeholder images to UICollectionView cells while actual images are being downloaded

I have a collection view that is being populated with images created from parsed data. It is being populated and updated by using an NSFetchedResultsController as the data source. The code is in Swift 4, so to do so I am using the data source methods for the collection view along with the delegate methods for the NSFetchedResultsControllerDelegate as shown here:
func collectionView (_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = fetchedResultsController.sections?[0].numberOfObjects {
return count
}else {
return 0
}
}
func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("Setting cell with photo")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! CollectionViewCell
let photoForCell = fetchedResultsController.object(at: indexPath)
cell.cellImage.image = UIImage(data: photoForCell.imageData!)
print(photoForCell.imageData!)
return cell
}
I am trying to calculate how many images were pulled in the parsed data and use this information to temporarily populate that many cells in the collection view with the same placeholder image in all of them. Then, once the images have all been downloaded and saved the NSFetchedResultsController will trigger the data source methods to refresh and populate the collection view with the new photos instead of the placeholder photos.
I have not been able to find any videos or posts that show how to do so. I tried creating a variable that holds the count of objects in the parsed data. That variable is then used in an if statement in the (:numberOfItemsInSection:) and (:cellForItemAt:) to determine whether it should populate the collection from saved photos or using the temporary photos like this:
func collectionView (_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.placeHolderCount != nil {
return placeHolderCount
}
if let count = fetchedResultsController.sections?[0].numberOfObjects {
return count
}else {
return 0
}
}
func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("Setting cell with photo")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! CollectionViewCell
let photoForCell = fetchedResultsController.object(at: indexPath)
if self.placeHolderCount != nil {
cell.cellImage.image = #imageLiteral(resourceName: "VirtualTourist_5112")
return cell
}
cell.cellImage.image = UIImage(data: photoForCell.imageData!)
print(photoForCell.imageData!)
return cell
}
Unfortunately, that causes the app to crash with an error saying "NSInvalidArgumentException reason: 'no object at index 0 in section at index 0"
During my troubleshooting, I found that this crash happens at the point when the view context is saved after downloading all the images. Everything in the application works perfectly including loading the downloaded images until I add the changes to the delegate methods. Does anyone know if I am on the right track? Should I be going about it in a completely different way? I have seen external libraries around that will accomplish this, but I cannot use any external libraries or frameworks for this particular application.

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
}

UICollectionView setting first cell to always be specific content

Hi so I'm using a side scrolling UICollectionView to display groups of people that the user makes. The groups are stored on my server and when the view loads, they load from the server. However I want the first cell to always be the same which is a cell which lets you create groups. This is the layout i need.
I know how to use multiple different custom cells, but how do I make it so the first cell is static and the cells after load content from my servers? Thanks :)
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return familyName.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell : AddGroupCollectionViewCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Add", forIndexPath: indexPath) as! AddGroupCollectionViewCell
return cell
} else {
let cell : FriendGroupsCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! FriendGroupsCell
cell.groupImage.image = UIImage(named: "pp")
cell.groupNameLabel.text = familyName[indexPath.row]
return cell
}
}
This is my code and it misses out the first person in the array because the index path skips over it. How can i modify this so it works
UICollectionViewCell is leveraging reusing techniques to improve performance. Remember this. Nothing can be static in a cell, because this cell later will be on another index.
You can use collectionView:cellForItemAtIndexPath: to make the first cell always load the same images/labels via indexPath.row == 0
You can use prepareReuse method to clean up the resources in the cell. So if cell No.2 is going to be the new No.1 cell, it get a chance to clean up old resources.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : AddGroupCollectionViewCell = collectionViewOutlet.dequeueReusableCellWithReuseIdentifier("Add", forIndexPath: indexPath) as! AddGroupCollectionViewCell
if indexPath.row == 0 {
cell.groupImage.image = UIImage(named: "new")
cell.groupNameLabel.text = "new"
} else {
cell.groupImage.image = UIImage(named: "pp")
cell.groupNameLabel.text = familyName[indexPath.row]
}
return cell
}